diff --git a/README.md b/README.md index e7e825b..a395b8a 100644 --- a/README.md +++ b/README.md @@ -17,19 +17,21 @@ result. Fluent assertions read like natural English. Features of `asserting`: -1. assertions are convenient to write and easy to read -2. helpful error messages in case of failing assertions -3. colored diffs between expected and actual values - (see ["Highlighted differences"](#highlighted-differences)) -4. chaining of multiple assertions on the same subject (see ["Chaining assertions"]) -5. concise and expressive assertions for more complex types like collections -6. field-by-field recursive comparison (see ["Field-by-field recursive comparison"]) :new: -7. soft assertions (execute multiple assertions before panicking) (see ["Soft assertions"]) -8. support for asserting custom types with provided assertions (see ["Asserting custom types"]) -9. provide a reasonable number of assertions out of the box -10. do not require that asserted types have to implement traits if it is not absolutely necessary -11. custom assertions (see ["Custom assertions"](#custom-assertions)) -12. support no-std environments +* assertions are convenient to write and easy to read +* helpful error messages in case of failing assertions +* colored diffs between expected and actual values + (see ["Highlighted differences"](#highlighted-differences)) +* chaining of multiple assertions on the same subject (see ["Chaining assertions"]) +* concise and expressive assertions for more complex types like collections +* field-by-field recursive comparison (see ["Field-by-field recursive comparison"]) :new: +* soft assertions (execute multiple assertions before panicking) (see ["Soft assertions"]) +* support for asserting custom types with provided assertions (see ["Asserting custom types"]) +* custom representation of values in failure reports or if a (foreign) type does not implement + [`Debug`] (see ["Type formatting (aka Representation)"]) +* provide a reasonable number of assertions out of the box +* do not require that asserted types have to implement traits if it is not absolutely necessary +* custom assertions (see ["Custom assertions"](#custom-assertions)) +* support no-std environments For an overview of the provided features and many examples on how to use `asserting` see the [crate-level documentation][docs-url]. @@ -71,8 +73,9 @@ default. ## Highlighted differences -`asserting` can highlight the differences between the expected value(s) and the actual value(s) when -printing assertion failures to the terminal. The colored diffs in assertion failures look like this: +`asserting` can highlight the differences between the expected value (s) and the actual value (s) +when printing assertion failures to the terminal. The colored diffs in assertion failures look like +this: ![colored diffs in terminal](examples/colored_diffs.png) @@ -89,9 +92,9 @@ It supports different variants of how differences are highlighted. | red-yellow | Differences are printed in the colors yellow and red. | | off | Switches off highlighting. The differences are not highlighted at all. | -The mode can be configured by setting the environment variable `ASSERTING_HIGHLIGHT_DIFFS` to one -of the modes in the table above. The value is case-insensitive. E.g., setting the environment -variable to values like `Red-Blue`, `Bold` or `OFF` works as well. +The mode can be configured by setting the environment variable `ASSERTING_HIGHLIGHT_DIFFS` to one of +the modes in the table above. The value is case-insensitive. E.g., setting the environment variable +to values like `Red-Blue`, `Bold` or `OFF` works as well. The intended way for configuring the highlighting mode is to set the environment variable in the configuration for `Cargo` by adding it to the `[env]` section in your `~/.cargo/config.toml` file: @@ -127,8 +130,7 @@ for all types that implement `PartialEq` with `E` being the type of the expec | is_equal_to | verify that the subject is equal to an expected value | | is_not_equal_to | verify that the subject is not equal to a specific value | -for all types that implement `PartialEq` and the subject is of the same type as the expected -value: +for all types that implement `PartialEq` and the subject is of the same type as the expected value: | assertion | description | |----------------|-----------------------------------------------------------------------------------------------| @@ -459,10 +461,10 @@ To start assertions on code, use the `assert_that_code!()` macro. `asserting` provides three kinds of custom assertions: 1. use any predicate function as a custom assertion (see "[predicate as custom assertion]") -2. property-based assertions can be used with any type that implements the related property - (see "[property-based assertions]") -3. write custom assertion methods by defining and implementing an extension trait - (see "[custom assertions]") +2. property-based assertions can be used with any type that implements the related property (see + "[property-based assertions]") +3. write custom assertion methods by defining and implementing an extension trait (see + "[custom assertions]") The mentioned references link to a chapter in the crate's documentation that describes the possibilities for custom assertions, including examples. @@ -491,6 +493,8 @@ possibilities for custom assertions, including examples. ["Field-by-field recursive comparison"]: https://docs.rs/asserting/latest/asserting/#field-by-field-recursive-comparison +["Type formatting (aka Representation)"]: https://docs.rs/asserting/latest/asserting/#type-formatting-aka-representation + ["soft assertions"]: https://docs.rs/asserting/#soft-assertions [custom assertions]: https://docs.rs/asserting/#custom-assertions diff --git a/examples/custom_assertion_reusing_existing.rs b/examples/custom_assertion_reusing_existing.rs index 5e384c5..656e67c 100644 --- a/examples/custom_assertion_reusing_existing.rs +++ b/examples/custom_assertion_reusing_existing.rs @@ -62,7 +62,7 @@ trait AssertSnake { // we implement the `AssertSnake` trait for a generic `S: Borrow` so that // the assertion method `has_body` can be called on owned and borrowed `Snake` // instances. -impl AssertSnake for Spec<'_, S, R> +impl AssertSnake for Spec<'_, S, D, R> where S: Borrow, R: FailingStrategy, diff --git a/src/assertions.rs b/src/assertions.rs index f2f2d43..5c0edad 100644 --- a/src/assertions.rs +++ b/src/assertions.rs @@ -10,8 +10,7 @@ //! assertions. #![allow(clippy::wrong_self_convention, clippy::return_self_not_must_use)] -use crate::spec::{CollectFailures, GetFailures, Spec}; -use crate::std::fmt::Debug; +use crate::spec::{CollectFailures, DebugRepresentation, GetFailures, Represent, Spec}; use crate::std::ops::RangeBounds; use crate::std::string::String; @@ -572,7 +571,7 @@ pub trait AssertOrder { /// assert_that!('r').is_in_range('H'..); /// assert_that!('N').is_in_range(..'n'); /// ``` -pub trait AssertInRange { +pub trait AssertInRange { /// Verifies that the subject is within the expected range. /// /// # Examples @@ -596,7 +595,8 @@ pub trait AssertInRange { #[track_caller] fn is_in_range(self, range: R) -> Self where - R: RangeBounds + Debug; + R: RangeBounds, + D: Represent; /// Verifies that the subject is not within the expected range. /// @@ -617,7 +617,8 @@ pub trait AssertInRange { #[track_caller] fn is_not_in_range(self, range: R) -> Self where - R: RangeBounds + Debug; + R: RangeBounds, + D: Represent; } /// Assert whether a numeric value is negative or positive. @@ -1364,7 +1365,7 @@ pub trait AssertEmptiness { /// assert_that!(&some_map).has_length(4); /// # } /// ``` -pub trait AssertHasLength { +pub trait AssertHasLength { /// Verifies that the subject has the expected length. /// /// # Examples @@ -1475,7 +1476,8 @@ pub trait AssertHasLength { #[track_caller] fn has_length_in_range(self, expected_range: U) -> Self where - U: RangeBounds + Debug; + U: RangeBounds, + D: Represent; /// Verifies that the subject has a length that is less than the expected /// length. @@ -1708,7 +1710,7 @@ pub trait AssertHasLength { /// assert_that!(subject).has_at_least_char_count(20); /// assert_that!(subject).has_at_least_char_count(25); /// ``` -pub trait AssertHasCharCount { +pub trait AssertHasCharCount { /// Verifies that the subject contains the expected number of characters. /// /// # Examples @@ -1744,7 +1746,8 @@ pub trait AssertHasCharCount { #[track_caller] fn has_char_count_in_range(self, range: U) -> Self where - U: RangeBounds + Debug; + U: RangeBounds, + D: Represent; /// Verifies that the subject contains less than the expected number of /// characters. @@ -3934,7 +3937,7 @@ where #[track_caller] fn each_element(self, assert: A) -> Self::Output where - A: Fn(Spec<'a, ::Item, CollectFailures>) -> B, + A: Fn(Spec<'a, ::Item, DebugRepresentation, CollectFailures>) -> B, B: GetFailures; /// Iterates over the elements of a collection or an iterator and executes @@ -3982,7 +3985,7 @@ where #[track_caller] fn any_element(self, assert: A) -> Self::Output where - A: Fn(Spec<'a, ::Item, CollectFailures>) -> B, + A: Fn(Spec<'a, ::Item, DebugRepresentation, CollectFailures>) -> B, B: GetFailures; } diff --git a/src/boolean/mod.rs b/src/boolean/mod.rs index 6f91481..0fbb0d5 100644 --- a/src/boolean/mod.rs +++ b/src/boolean/mod.rs @@ -4,13 +4,15 @@ use crate::assertions::AssertBoolean; use crate::colored::{mark_missing, mark_unexpected}; use crate::expectations::{IsFalse, IsTrue, is_false, is_true}; use crate::spec::{ - DiffFormat, Expectation, Expecting, Expression, FailingStrategy, Invertible, Spec, + DiffFormat, Expectation, Expecting, Expression, FailingStrategy, Invertible, Represent, + Represented, Spec, }; use crate::std::format; use crate::std::string::String; -impl AssertBoolean for Spec<'_, bool, R> +impl AssertBoolean for Spec<'_, bool, D, R> where + D: Represent, R: FailingStrategy, { fn is_true(self) -> Self { @@ -22,7 +24,10 @@ where } } -impl Expectation for IsTrue { +impl Expectation for IsTrue +where + D: Represent, +{ fn test(&mut self, subject: &bool) -> bool { *subject } @@ -32,20 +37,24 @@ impl Expectation for IsTrue { expression: &Expression<'_>, actual: &bool, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { - let marked_actual = mark_unexpected(&actual, format); - let marked_expected = mark_missing(&!inverted, format); + let marked_actual = mark_unexpected(actual, representation, format); + let marked_expected = mark_missing(&!inverted, representation, format); + let represented_expected = Represented::from((&true, representation)); format!( - "expected {expression} to be {:?}\n but was: {marked_actual}\n expected: {marked_expected}", - true + "expected {expression} to be {represented_expected:?}\n but was: {marked_actual}\n expected: {marked_expected}", ) } } impl Invertible for IsTrue {} -impl Expectation for IsFalse { +impl Expectation for IsFalse +where + D: Represent, +{ fn test(&mut self, subject: &bool) -> bool { !*subject } @@ -55,13 +64,14 @@ impl Expectation for IsFalse { expression: &Expression<'_>, actual: &bool, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { - let marked_actual = mark_unexpected(actual, format); - let marked_expected = mark_missing(&inverted, format); + let marked_actual = mark_unexpected(actual, representation, format); + let marked_expected = mark_missing(&inverted, representation, format); + let represented_expected = Represented::from((&false, representation)); format!( - "expected {expression} to be {:?}\n but was: {marked_actual}\n expected: {marked_expected}", - false + "expected {expression} to be {represented_expected:?}\n but was: {marked_actual}\n expected: {marked_expected}", ) } } diff --git a/src/char/mod.rs b/src/char/mod.rs index 907151c..48644c1 100644 --- a/src/char/mod.rs +++ b/src/char/mod.rs @@ -1,18 +1,20 @@ use crate::assertions::AssertChar; -use crate::colored::{mark_missing_string, mark_unexpected_char}; +use crate::colored::{mark_missing, mark_unexpected}; use crate::expectations::{ IsAlphabetic, IsAlphanumeric, IsAscii, IsControlChar, IsDigit, IsLowerCase, IsUpperCase, IsWhitespace, is_alphabetic, is_alphanumeric, is_ascii, is_control_char, is_digit, is_lower_case, is_upper_case, is_whitespace, }; use crate::spec::{ - DiffFormat, Expectation, Expecting, Expression, FailingStrategy, Invertible, Spec, + DiffFormat, DisplayRepresentation, Expectation, Expecting, Expression, FailingStrategy, + Invertible, Represent, Spec, }; use crate::std::format; use crate::std::string::{String, ToString}; -impl AssertChar for Spec<'_, char, R> +impl AssertChar for Spec<'_, char, D, R> where + D: Represent + Represent, R: FailingStrategy, { fn is_lowercase(self) -> Self { @@ -48,8 +50,9 @@ where } } -impl AssertChar for Spec<'_, &char, R> +impl AssertChar for Spec<'_, &char, D, R> where + D: Represent + Represent, R: FailingStrategy, { fn is_lowercase(self) -> Self { @@ -85,7 +88,10 @@ where } } -impl Expectation for IsLowerCase { +impl Expectation for IsLowerCase +where + D: Represent, +{ fn test(&mut self, subject: &char) -> bool { subject.is_lowercase() } @@ -95,6 +101,7 @@ impl Expectation for IsLowerCase { expression: &Expression<'_>, actual: &char, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let (not, expected) = if inverted { @@ -102,19 +109,22 @@ impl Expectation for IsLowerCase { } else { ("", actual.to_lowercase().to_string()) }; - let marked_actual = mark_unexpected_char(*actual, format); - let marked_expected = mark_missing_string(&expected, format); + let marked_actual = mark_unexpected(actual, representation, format); + let marked_expected = mark_missing(&expected, &DisplayRepresentation, format); format!( - "expected {expression} to be {not}lowercase\n but was: {marked_actual}\n expected: {marked_expected}" + "expected {expression} to be {not}lowercase\n but was: {marked_actual}\n expected: '{marked_expected}'" ) } } impl Invertible for IsLowerCase {} -impl Expectation<&char> for IsLowerCase { +impl Expectation<&char, D> for IsLowerCase +where + D: Represent, +{ fn test(&mut self, subject: &&char) -> bool { - >::test(self, subject) + >::test(self, subject) } fn message( @@ -122,13 +132,24 @@ impl Expectation<&char> for IsLowerCase { expression: &Expression<'_>, actual: &&char, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { - >::message(self, expression, actual, inverted, format) + >::message( + self, + expression, + actual, + inverted, + representation, + format, + ) } } -impl Expectation for IsUpperCase { +impl Expectation for IsUpperCase +where + D: Represent, +{ fn test(&mut self, subject: &char) -> bool { subject.is_uppercase() } @@ -138,6 +159,7 @@ impl Expectation for IsUpperCase { expression: &Expression<'_>, actual: &char, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let (not, expected) = if inverted { @@ -145,19 +167,22 @@ impl Expectation for IsUpperCase { } else { ("", actual.to_uppercase().to_string()) }; - let marked_actual = mark_unexpected_char(*actual, format); - let marked_expected = mark_missing_string(&expected, format); + let marked_actual = mark_unexpected(actual, representation, format); + let marked_expected = mark_missing(&expected, &DisplayRepresentation, format); format!( - "expected {expression} to be {not}uppercase\n but was: {marked_actual}\n expected: {marked_expected}" + "expected {expression} to be {not}uppercase\n but was: {marked_actual}\n expected: '{marked_expected}'" ) } } impl Invertible for IsUpperCase {} -impl Expectation<&char> for IsUpperCase { +impl Expectation<&char, D> for IsUpperCase +where + D: Represent, +{ fn test(&mut self, subject: &&char) -> bool { - >::test(self, subject) + >::test(self, subject) } fn message( @@ -165,13 +190,24 @@ impl Expectation<&char> for IsUpperCase { expression: &Expression<'_>, actual: &&char, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { - >::message(self, expression, actual, inverted, format) + >::message( + self, + expression, + actual, + inverted, + representation, + format, + ) } } -impl Expectation for IsAscii { +impl Expectation for IsAscii +where + D: Represent, +{ fn test(&mut self, subject: &char) -> bool { subject.is_ascii() } @@ -181,10 +217,11 @@ impl Expectation for IsAscii { expression: &Expression<'_>, actual: &char, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let not = if inverted { "not " } else { "" }; - let marked_actual = mark_unexpected_char(*actual, format); + let marked_actual = mark_unexpected(actual, representation, format); format!( "expected {expression} to be {not}an ASCII character\n but was: {marked_actual}\n expected: {not}an ASCII character" ) @@ -193,9 +230,12 @@ impl Expectation for IsAscii { impl Invertible for IsAscii {} -impl Expectation<&char> for IsAscii { +impl Expectation<&char, D> for IsAscii +where + D: Represent, +{ fn test(&mut self, subject: &&char) -> bool { - >::test(self, subject) + >::test(self, subject) } fn message( @@ -203,13 +243,24 @@ impl Expectation<&char> for IsAscii { expression: &Expression<'_>, actual: &&char, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { - >::message(self, expression, actual, inverted, format) + >::message( + self, + expression, + actual, + inverted, + representation, + format, + ) } } -impl Expectation for IsAlphabetic { +impl Expectation for IsAlphabetic +where + D: Represent, +{ fn test(&mut self, subject: &char) -> bool { subject.is_alphabetic() } @@ -219,10 +270,11 @@ impl Expectation for IsAlphabetic { expression: &Expression<'_>, actual: &char, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let not = if inverted { "not " } else { "" }; - let marked_actual = mark_unexpected_char(*actual, format); + let marked_actual = mark_unexpected(actual, representation, format); format!( "expected {expression} to be {not}an alphabetic character\n but was: {marked_actual}\n expected: {not}an alphabetic character" ) @@ -231,9 +283,12 @@ impl Expectation for IsAlphabetic { impl Invertible for IsAlphabetic {} -impl Expectation<&char> for IsAlphabetic { +impl Expectation<&char, D> for IsAlphabetic +where + D: Represent, +{ fn test(&mut self, subject: &&char) -> bool { - >::test(self, subject) + >::test(self, subject) } fn message( @@ -241,13 +296,24 @@ impl Expectation<&char> for IsAlphabetic { expression: &Expression<'_>, actual: &&char, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { - >::message(self, expression, actual, inverted, format) + >::message( + self, + expression, + actual, + inverted, + representation, + format, + ) } } -impl Expectation for IsAlphanumeric { +impl Expectation for IsAlphanumeric +where + D: Represent, +{ fn test(&mut self, subject: &char) -> bool { subject.is_alphanumeric() } @@ -257,10 +323,11 @@ impl Expectation for IsAlphanumeric { expression: &Expression<'_>, actual: &char, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let not = if inverted { "not " } else { "" }; - let marked_actual = mark_unexpected_char(*actual, format); + let marked_actual = mark_unexpected(actual, representation, format); format!( "expected {expression} to be {not}an alphanumeric character\n but was: {marked_actual}\n expected: {not}an alphanumeric character" ) @@ -269,9 +336,12 @@ impl Expectation for IsAlphanumeric { impl Invertible for IsAlphanumeric {} -impl Expectation<&char> for IsAlphanumeric { +impl Expectation<&char, D> for IsAlphanumeric +where + D: Represent, +{ fn test(&mut self, subject: &&char) -> bool { - >::test(self, subject) + >::test(self, subject) } fn message( @@ -279,13 +349,24 @@ impl Expectation<&char> for IsAlphanumeric { expression: &Expression<'_>, actual: &&char, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { - >::message(self, expression, actual, inverted, format) + >::message( + self, + expression, + actual, + inverted, + representation, + format, + ) } } -impl Expectation for IsControlChar { +impl Expectation for IsControlChar +where + D: Represent, +{ fn test(&mut self, subject: &char) -> bool { subject.is_control() } @@ -295,10 +376,11 @@ impl Expectation for IsControlChar { expression: &Expression<'_>, actual: &char, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let not = if inverted { "not " } else { "" }; - let marked_actual = mark_unexpected_char(*actual, format); + let marked_actual = mark_unexpected(actual, representation, format); format!( "expected {expression} to be {not}a control character\n but was: {marked_actual}\n expected: {not}a control character" ) @@ -307,9 +389,12 @@ impl Expectation for IsControlChar { impl Invertible for IsControlChar {} -impl Expectation<&char> for IsControlChar { +impl Expectation<&char, D> for IsControlChar +where + D: Represent, +{ fn test(&mut self, subject: &&char) -> bool { - >::test(self, subject) + >::test(self, subject) } fn message( @@ -317,13 +402,24 @@ impl Expectation<&char> for IsControlChar { expression: &Expression<'_>, actual: &&char, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { - >::message(self, expression, actual, inverted, format) + >::message( + self, + expression, + actual, + inverted, + representation, + format, + ) } } -impl Expectation for IsDigit { +impl Expectation for IsDigit +where + D: Represent, +{ fn test(&mut self, subject: &char) -> bool { subject.is_digit(self.radix) } @@ -333,11 +429,12 @@ impl Expectation for IsDigit { expression: &Expression<'_>, actual: &char, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let not = if inverted { "not " } else { "" }; let radix = self.radix; - let marked_actual = mark_unexpected_char(*actual, format); + let marked_actual = mark_unexpected(actual, representation, format); format!( "expected {expression} to be {not}a digit in the radix {radix}\n but was: {marked_actual}\n expected: {not}a digit in the radix {radix}" ) @@ -346,9 +443,12 @@ impl Expectation for IsDigit { impl Invertible for IsDigit {} -impl Expectation<&char> for IsDigit { +impl Expectation<&char, D> for IsDigit +where + D: Represent, +{ fn test(&mut self, subject: &&char) -> bool { - >::test(self, subject) + >::test(self, subject) } fn message( @@ -356,13 +456,24 @@ impl Expectation<&char> for IsDigit { expression: &Expression<'_>, actual: &&char, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { - >::message(self, expression, actual, inverted, format) + >::message( + self, + expression, + actual, + inverted, + representation, + format, + ) } } -impl Expectation for IsWhitespace { +impl Expectation for IsWhitespace +where + D: Represent, +{ fn test(&mut self, subject: &char) -> bool { subject.is_whitespace() } @@ -372,10 +483,11 @@ impl Expectation for IsWhitespace { expression: &Expression<'_>, actual: &char, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let not = if inverted { "not " } else { "" }; - let marked_actual = mark_unexpected_char(*actual, format); + let marked_actual = mark_unexpected(actual, representation, format); format!( "expected {expression} to be {not}whitespace\n but was: {marked_actual}\n expected: {not}whitespace" ) @@ -384,9 +496,12 @@ impl Expectation for IsWhitespace { impl Invertible for IsWhitespace {} -impl Expectation<&char> for IsWhitespace { +impl Expectation<&char, D> for IsWhitespace +where + D: Represent, +{ fn test(&mut self, subject: &&char) -> bool { - >::test(self, subject) + >::test(self, subject) } fn message( @@ -394,9 +509,17 @@ impl Expectation<&char> for IsWhitespace { expression: &Expression<'_>, actual: &&char, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { - >::message(self, expression, actual, inverted, format) + >::message( + self, + expression, + actual, + inverted, + representation, + format, + ) } } diff --git a/src/char/tests.rs b/src/char/tests.rs index 8899b35..24b5121 100644 --- a/src/char/tests.rs +++ b/src/char/tests.rs @@ -13,8 +13,8 @@ fn verify_char_is_lowercase_fails() { assert_eq!( failures, &[r"expected subject to be lowercase - but was: M - expected: m + but was: 'M' + expected: 'm' "] ); } @@ -31,8 +31,8 @@ fn verify_borrowed_char_is_lowercase_fails() { assert_eq!( failures, &[r"expected subject to be lowercase - but was: M - expected: m + but was: 'M' + expected: 'm' "] ); } @@ -49,8 +49,8 @@ fn verify_char_is_uppercase_fails() { assert_eq!( failures, &[r"expected subject to be uppercase - but was: k - expected: K + but was: 'k' + expected: 'K' "] ); } @@ -67,8 +67,8 @@ fn verify_borrowed_char_is_uppercase_fails() { assert_eq!( failures, &[r"expected subject to be uppercase - but was: x - expected: X + but was: 'x' + expected: 'X' "] ); } @@ -85,7 +85,7 @@ fn verify_char_is_ascii_fails() { assert_eq!( failures, &[r"expected subject to be an ASCII character - but was: € + but was: '€' expected: an ASCII character "] ); @@ -103,7 +103,7 @@ fn verify_borrowed_char_is_ascii_fails() { assert_eq!( failures, &[r"expected subject to be an ASCII character - but was: ❤ + but was: '❤' expected: an ASCII character "] ); @@ -121,7 +121,7 @@ fn verify_char_is_alphabetic_fails() { assert_eq!( failures, &[r"expected subject to be an alphabetic character - but was: 1 + but was: '1' expected: an alphabetic character "] ); @@ -139,7 +139,7 @@ fn verify_borrowed_char_is_alphabetic_fails() { assert_eq!( failures, &[r"expected subject to be an alphabetic character - but was: @ + but was: '@' expected: an alphabetic character "] ); @@ -158,7 +158,7 @@ fn verify_char_is_alphanumeric_fails() { assert_eq!( failures, &[r"expected subject to be an alphanumeric character - but was: @ + but was: '@' expected: an alphanumeric character "] ); @@ -177,7 +177,7 @@ fn verify_borrowed_char_is_alphanumeric_fails() { assert_eq!( failures, &[r"expected subject to be an alphanumeric character - but was: + + but was: '+' expected: an alphanumeric character "] ); @@ -195,7 +195,7 @@ fn verify_char_is_control_char_fails() { assert_eq!( failures, &[r"expected subject to be a control character - but was: [ + but was: '[' expected: a control character "] ); @@ -213,7 +213,7 @@ fn verify_borrowed_char_is_control_char_fails() { assert_eq!( failures, &[r"expected subject to be a control character - but was: @ + but was: '@' expected: a control character "] ); @@ -231,7 +231,7 @@ fn verify_char_is_digit_in_radix_10_fails() { assert_eq!( failures, &[r"expected subject to be a digit in the radix 10 - but was: A + but was: 'A' expected: a digit in the radix 10 "] ); @@ -249,7 +249,7 @@ fn verify_borrowed_char_is_digit_in_radix_10_fails() { assert_eq!( failures, &[r"expected subject to be a digit in the radix 10 - but was: F + but was: 'F' expected: a digit in the radix 10 "] ); @@ -267,7 +267,7 @@ fn verify_char_is_digit_in_radix_16_fails() { assert_eq!( failures, &[r"expected subject to be a digit in the radix 16 - but was: G + but was: 'G' expected: a digit in the radix 16 "] ); @@ -285,7 +285,7 @@ fn verify_borrowed_char_is_digit_in_radix_16_fails() { assert_eq!( failures, &[r"expected subject to be a digit in the radix 16 - but was: g + but was: 'g' expected: a digit in the radix 16 "] ); @@ -303,7 +303,7 @@ fn verify_char_is_digit_in_radix_7_fails() { assert_eq!( failures, &[r"expected subject to be a digit in the radix 7 - but was: 7 + but was: '7' expected: a digit in the radix 7 "] ); @@ -321,7 +321,7 @@ fn verify_borrowed_char_is_digit_in_radix_7_fails() { assert_eq!( failures, &[r"expected subject to be a digit in the radix 7 - but was: 9 + but was: '9' expected: a digit in the radix 7 "] ); @@ -339,7 +339,7 @@ fn verify_char_is_whitespace_fails() { assert_eq!( failures, &[r"expected subject to be whitespace - but was: _ + but was: '_' expected: whitespace "] ); @@ -357,7 +357,7 @@ fn verify_borrowed_char_is_whitespace_fails() { assert_eq!( failures, &[r"expected subject to be whitespace - but was: = + but was: '=' expected: whitespace "] ); diff --git a/src/char_count.rs b/src/char_count.rs index 1029e0e..c760341 100644 --- a/src/char_count.rs +++ b/src/char_count.rs @@ -8,15 +8,17 @@ use crate::expectations::{ has_char_count, has_char_count_greater_than, has_char_count_in_range, has_char_count_less_than, }; use crate::properties::CharCountProperty; -use crate::spec::{DiffFormat, Expectation, Expecting, Expression, FailingStrategy, Spec}; -use crate::std::fmt::Debug; +use crate::spec::{ + DiffFormat, Expectation, Expecting, Expression, FailingStrategy, Represent, Represented, Spec, +}; use crate::std::format; use crate::std::ops::RangeBounds; use crate::std::string::String; -impl AssertHasCharCount for Spec<'_, S, R> +impl AssertHasCharCount for Spec<'_, S, D, R> where - S: CharCountProperty + Debug, + S: CharCountProperty, + D: Represent, R: FailingStrategy, { fn has_char_count(self, expected_char_count: usize) -> Self { @@ -25,7 +27,8 @@ where fn has_char_count_in_range(self, expected_range: U) -> Self where - U: RangeBounds + Debug, + U: RangeBounds, + D: Represent, { self.expecting(has_char_count_in_range(expected_range)) } @@ -47,9 +50,10 @@ where } } -impl Expectation for HasCharCount +impl Expectation for HasCharCount where - S: CharCountProperty + Debug, + S: CharCountProperty, + D: Represent, { fn test(&mut self, subject: &S) -> bool { subject.char_count_property() == self.expected_char_count @@ -60,22 +64,24 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let not = if inverted { "not in " } else { "" }; - let marked_actual = mark_unexpected(&actual.char_count_property(), format); - let marked_expected = mark_missing(&self.expected_char_count, format); + let marked_actual = mark_unexpected(&actual.char_count_property(), representation, format); + let marked_expected = mark_missing(&self.expected_char_count, representation, format); + let expected_char_count = Represented::from((&self.expected_char_count, representation)); format!( - "expected {expression} to {not}have a char count of {:?}\n but was: {marked_actual}\n expected: {not}{marked_expected}", - self.expected_char_count + "expected {expression} to {not}have a char count of {expected_char_count:?}\n but was: {marked_actual}\n expected: {not}{marked_expected}", ) } } -impl Expectation for HasCharCountInRange +impl Expectation for HasCharCountInRange where - S: CharCountProperty + Debug, - R: RangeBounds + Debug, + S: CharCountProperty, + R: RangeBounds, + D: Represent + Represent, { fn test(&mut self, subject: &S) -> bool { self.expected_range.contains(&subject.char_count_property()) @@ -86,21 +92,23 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let not = if inverted { "not in " } else { "" }; - let marked_actual = mark_unexpected(&actual.char_count_property(), format); - let marked_expected = mark_missing(&self.expected_range, format); + let marked_actual = mark_unexpected(&actual.char_count_property(), representation, format); + let marked_expected = mark_missing(&self.expected_range, representation, format); + let expected_range = Represented::from((&self.expected_range, representation)); format!( - "expected {expression} to {not}have a char count within {:?}\n but was: {marked_actual}\n expected: {not}{marked_expected}", - self.expected_range, + "expected {expression} to {not}have a char count within {expected_range:?}\n but was: {marked_actual}\n expected: {not}{marked_expected}", ) } } -impl Expectation for HasCharCountLessThan +impl Expectation for HasCharCountLessThan where - S: CharCountProperty + Debug, + S: CharCountProperty, + D: Represent, { fn test(&mut self, subject: &S) -> bool { subject.char_count_property() < self.expected_char_count @@ -111,11 +119,12 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let (not, cmp) = if inverted { ("not ", ">=") } else { ("", "<") }; - let marked_actual = mark_unexpected(&actual.char_count_property(), format); - let marked_expected = mark_missing(&self.expected_char_count, format); + let marked_actual = mark_unexpected(&actual.char_count_property(), representation, format); + let marked_expected = mark_missing(&self.expected_char_count, representation, format); format!( "expected {expression} to {not}have a char count less than {:?}\n but was: {marked_actual}\n expected: {cmp} {marked_expected}", self.expected_char_count, @@ -123,9 +132,10 @@ where } } -impl Expectation for HasCharCountGreaterThan +impl Expectation for HasCharCountGreaterThan where - S: CharCountProperty + Debug, + S: CharCountProperty, + D: Represent, { fn test(&mut self, subject: &S) -> bool { subject.char_count_property() > self.expected_char_count @@ -136,21 +146,23 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let (not, cmp) = if inverted { ("not ", "<=") } else { ("", ">") }; - let marked_actual = mark_unexpected(&actual.char_count_property(), format); - let marked_expected = mark_missing(&self.expected_char_count, format); + let marked_actual = mark_unexpected(&actual.char_count_property(), representation, format); + let marked_expected = mark_missing(&self.expected_char_count, representation, format); + let expected_char_count = Represented::from((&self.expected_char_count, representation)); format!( - "expected {expression} to {not}have a char count greater than {:?}\n but was: {marked_actual}\n expected: {cmp} {marked_expected}", - self.expected_char_count, + "expected {expression} to {not}have a char count greater than {expected_char_count:?}\n but was: {marked_actual}\n expected: {cmp} {marked_expected}", ) } } -impl Expectation for HasAtMostCharCount +impl Expectation for HasAtMostCharCount where - S: CharCountProperty + Debug, + S: CharCountProperty, + D: Represent, { fn test(&mut self, subject: &S) -> bool { subject.char_count_property() <= self.expected_char_count @@ -161,21 +173,23 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let (not, cmp) = if inverted { ("not ", ">") } else { ("", "<=") }; - let marked_actual = mark_unexpected(&actual.char_count_property(), format); - let marked_expected = mark_missing(&self.expected_char_count, format); + let marked_actual = mark_unexpected(&actual.char_count_property(), representation, format); + let marked_expected = mark_missing(&self.expected_char_count, representation, format); + let expected_char_count = Represented::from((&self.expected_char_count, representation)); format!( - "expected {expression} to {not}have at most a char count of {:?}\n but was: {marked_actual}\n expected: {cmp} {marked_expected}", - self.expected_char_count, + "expected {expression} to {not}have at most a char count of {expected_char_count:?}\n but was: {marked_actual}\n expected: {cmp} {marked_expected}", ) } } -impl Expectation for HasAtLeastCharCount +impl Expectation for HasAtLeastCharCount where S: CharCountProperty, + D: Represent, { fn test(&mut self, subject: &S) -> bool { subject.char_count_property() >= self.expected_char_count @@ -186,14 +200,15 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let (not, cmp) = if inverted { ("not ", "<") } else { ("", ">=") }; - let marked_actual = mark_unexpected(&actual.char_count_property(), format); - let marked_expected = mark_missing(&self.expected_char_count, format); + let marked_actual = mark_unexpected(&actual.char_count_property(), representation, format); + let marked_expected = mark_missing(&self.expected_char_count, representation, format); + let expected_char_count = Represented::from((&self.expected_char_count, representation)); format!( - "expected {expression} to {not}have at least a char count of {:?}\n but was: {marked_actual}\n expected: {cmp} {marked_expected}", - self.expected_char_count, + "expected {expression} to {not}have at least a char count of {expected_char_count:?}\n but was: {marked_actual}\n expected: {cmp} {marked_expected}", ) } } diff --git a/src/colored/mod.rs b/src/colored/mod.rs index 1b80053..a583f5b 100644 --- a/src/colored/mod.rs +++ b/src/colored/mod.rs @@ -51,23 +51,18 @@ pub use with_colored_feature::{ diff_format_for_mode, }; -use crate::spec::{DiffFormat, Highlight}; -use crate::std::fmt::Debug; +use crate::spec::{DiffFormat, DisplayRepresentation, Highlight, Represent, Represented}; use crate::std::format; use crate::std::string::{String, ToString}; use crate::std::vec::Vec; use hashbrown::HashSet; #[cfg(feature = "colored")] use with_colored_feature::{ - configured_diff_format_impl, mark_diff_impl, mark_missing_char_impl, mark_missing_impl, - mark_missing_string_impl, mark_unexpected_char_impl, mark_unexpected_impl, - mark_unexpected_string_impl, + configured_diff_format_impl, mark_diff_impl, mark_missing_impl, mark_unexpected_impl, }; #[cfg(not(feature = "colored"))] use without_colored_feature::{ - configured_diff_format_impl, mark_diff_impl, mark_missing_char_impl, mark_missing_impl, - mark_missing_string_impl, mark_unexpected_char_impl, mark_unexpected_impl, - mark_unexpected_string_impl, + configured_diff_format_impl, mark_diff_impl, mark_missing_impl, mark_unexpected_impl, }; const NO_HIGHLIGHT: Highlight = Highlight { start: "", end: "" }; @@ -131,7 +126,7 @@ pub fn configured_diff_format() -> DiffFormat { } /// Highlights differences between the expected and the actual value and returns -/// the debug formatted values with marked differences. +/// the debug-formatted values with marked differences. /// /// The style for marking differences is determined by the provided /// [`DiffFormat`]. @@ -144,7 +139,7 @@ pub fn configured_diff_format() -> DiffFormat { /// /// It returns a tuple of two `String`s. The first string contains the actual /// value, and the second one contains the expected value. Both strings -/// represent their according value as debug formatted string with differences +/// represent their according value as debug-formatted string with differences /// highlighted. /// /// # Examples @@ -154,12 +149,20 @@ pub fn configured_diff_format() -> DiffFormat { /// # fn main() {} /// # #[cfg(feature = "colored")] /// # fn main() { -/// use asserting::colored::{mark_diff, DIFF_FORMAT_RED_GREEN}; +/// use asserting::{ +/// colored::{mark_diff, DIFF_FORMAT_RED_GREEN}, +/// spec::DebugRepresentation, +/// }; /// /// let actual = "Hello Welt!"; /// let expected = "Hello World!"; /// -/// let (marked_actual, marked_expected) = mark_diff(&actual, &expected, &DIFF_FORMAT_RED_GREEN); +/// let (marked_actual, marked_expected) = mark_diff( +/// &actual, +/// &expected, +/// &DebugRepresentation, +/// &DIFF_FORMAT_RED_GREEN +/// ); /// /// assert_eq!(marked_actual, "\"Hello W\u{1b}[31me\u{1b}[0ml\u{1b}[31mt\u{1b}[0m!\""); /// assert_eq!(marked_expected, "\"Hello W\u{1b}[32mor\u{1b}[0ml\u{1b}[32md\u{1b}[0m!\""); @@ -171,7 +174,10 @@ pub fn configured_diff_format() -> DiffFormat { /// # fn main() {} /// # #[cfg(feature = "colored")] /// # fn main() { -/// use asserting::colored::{mark_diff, DIFF_FORMAT_RED_BLUE}; +/// use asserting::{ +/// colored::{mark_diff, DIFF_FORMAT_RED_BLUE}, +/// spec::DebugRepresentation, +/// }; /// /// #[derive(Debug)] /// struct Pos { @@ -182,17 +188,30 @@ pub fn configured_diff_format() -> DiffFormat { /// let actual = Pos { x: 45, y: -21 }; /// let expected = Pos { x: -45, y: -33 }; /// -/// let (marked_actual, marked_expected) = mark_diff(&actual, &expected, &DIFF_FORMAT_RED_BLUE); +/// let (marked_actual, marked_expected) = mark_diff( +/// &actual, +/// &expected, +/// &DebugRepresentation, +/// &DIFF_FORMAT_RED_BLUE +/// ); /// /// assert_eq!(marked_actual, "Pos { x: 45, y: -\u{1b}[31m21\u{1b}[0m }"); /// assert_eq!(marked_expected, "Pos { x: \u{1b}[34m-\u{1b}[0m45, y: -\u{1b}[34m33\u{1b}[0m }"); /// # } /// ``` -pub fn mark_diff(actual: &S, expected: &E, format: &DiffFormat) -> (String, String) +pub fn mark_diff( + actual: &S, + expected: &E, + representation: &D, + format: &DiffFormat, +) -> (String, String) where - S: Debug + ?Sized, - E: Debug + ?Sized, + S: ?Sized, + E: ?Sized, + D: Represent + Represent, { + let actual = Represented::from((actual, representation)); + let expected = Represented::from((expected, representation)); let actual = format!("{actual:?}"); let expected = format!("{expected:?}"); mark_diff_impl(&actual, &expected, format) @@ -218,60 +237,22 @@ pub fn mark_diff_str(actual: &str, expected: &str, format: &DiffFormat) -> (Stri /// Highlights the given value as "unexpected value" using the color for /// unexpected values or bold as specified by the given [`DiffFormat`]. -pub fn mark_unexpected(value: &T, format: &DiffFormat) -> String +pub fn mark_unexpected(value: &T, representation: &D, format: &DiffFormat) -> String where - T: Debug + ?Sized, + T: ?Sized, + D: Represent, { - mark_unexpected_impl(value, format) + mark_unexpected_impl(value, representation, format) } /// Highlights the given value as "missing value" using the color for /// "missing values" as specified by the given [`DiffFormat`]. -pub fn mark_missing(value: &T, format: &DiffFormat) -> String +pub fn mark_missing(value: &T, representation: &D, format: &DiffFormat) -> String where - T: Debug + ?Sized, + T: ?Sized, + D: Represent, { - mark_missing_impl(value, format) -} - -/// Highlights the given string as "unexpected value" using the color for -/// unexpected values or bold as specified by the given [`DiffFormat`]. -/// -/// When using this function in comparison to [`mark_unexpected`], the returned -/// string does not contain quotes at the start and end of the string as they -/// appear in the debug formatted string returned by [`mark_unexpected`]. -pub fn mark_unexpected_string(string: &str, format: &DiffFormat) -> String { - mark_unexpected_string_impl(string, format) -} - -/// Highlights the given string as "missing value" using the color for -/// missing values as specified by the given [`DiffFormat`]. -/// -/// When using this function in comparison to [`mark_missing`], the returned -/// string does not contain quotes at the start and end of the string as they -/// appear in the debug formatted string returned by [`mark_missing`]. -pub fn mark_missing_string(string: &str, format: &DiffFormat) -> String { - mark_missing_string_impl(string, format) -} - -/// Highlights the given character as "unexpected value" using the color for -/// unexpected values or bold as specified by the given [`DiffFormat`]. -/// -/// When using this function in comparison to [`mark_unexpected`], the returned -/// string does not contain single quotes around the character as they -/// appear in the debug formatted string returned by [`mark_unexpected`]. -pub fn mark_unexpected_char(character: char, format: &DiffFormat) -> String { - mark_unexpected_char_impl(character, format) -} - -/// Highlights the given character as "missing value" using the color for -/// missing values as specified by the given [`DiffFormat`]. -/// -/// When using this function in comparison to [`mark_missing`], the returned -/// string does not contain single quotes around the character as they -/// appear in the debug formatted string returned by [`mark_missing`]. -pub fn mark_missing_char(character: char, format: &DiffFormat) -> String { - mark_missing_char_impl(character, format) + mark_missing_impl(value, representation, format) } /// Highlights a substring within a string using the color for unexpected values @@ -303,7 +284,14 @@ pub fn mark_unexpected_substring_in_string( substring: &str, format: &DiffFormat, ) -> String { - mark_substring_in_string(string, substring, format, mark_unexpected_string) + mark_substring_in_string( + string, + substring, + format, + |string, _: &DisplayRepresentation, format| { + mark_unexpected(string, &DisplayRepresentation, format) + }, + ) } /// Highlights a substring within a string using the color for missing values @@ -335,7 +323,14 @@ pub fn mark_missing_substring_in_string( substring: &str, format: &DiffFormat, ) -> String { - mark_substring_in_string(string, substring, format, mark_missing_string) + mark_substring_in_string( + string, + substring, + format, + |string, _: &DisplayRepresentation, format| { + mark_missing(string, &DisplayRepresentation, format) + }, + ) } fn mark_substring_in_string( @@ -345,13 +340,13 @@ fn mark_substring_in_string( mark: F, ) -> String where - F: Fn(&str, &DiffFormat) -> String, + F: Fn(&str, &DisplayRepresentation, &DiffFormat) -> String, { if let Some(position) = string.find(substring) { let length = substring.len(); let begin = &string[..position]; let end = &string[position + length..]; - let marked_substr = mark(substring, format); + let marked_substr = mark(substring, &DisplayRepresentation, format); format!("{begin}{marked_substr}{end}") } else { string.to_string() @@ -387,7 +382,9 @@ pub fn mark_unexpected_char_in_string( character: char, format: &DiffFormat, ) -> String { - mark_char_in_string(string, character, format, mark_unexpected_string) + mark_char_in_string(string, character, format, |string, format| { + mark_unexpected(string, &DisplayRepresentation, format) + }) } /// Highlights all occurences of a character within a string using the color for @@ -415,7 +412,9 @@ pub fn mark_unexpected_char_in_string( /// # } /// ``` pub fn mark_missing_char_in_string(string: &str, character: char, format: &DiffFormat) -> String { - mark_char_in_string(string, character, format, mark_missing_string) + mark_char_in_string(string, character, format, |string, format| { + mark_missing(string, &DisplayRepresentation, format) + }) } fn mark_char_in_string(string: &str, character: char, format: &DiffFormat, mark: F) -> String @@ -562,7 +561,10 @@ fn mark_selected_chars_in_string( /// # fn main() {} /// # #[cfg(feature = "colored")] /// # fn main() { -/// use asserting::colored::{mark_missing, mark_selected_items_in_collection, DIFF_FORMAT_RED_BLUE}; +/// use asserting::{ +/// colored::{mark_missing, mark_selected_items_in_collection, DIFF_FORMAT_RED_BLUE}, +/// spec::DebugRepresentation, +/// }; /// use hashbrown::HashSet; /// /// let collection = [1, 2, 3, 4, 5]; @@ -571,6 +573,7 @@ fn mark_selected_chars_in_string( /// let marked_collection = mark_selected_items_in_collection( /// &collection, /// &selected_items, +/// &DebugRepresentation, /// &DIFF_FORMAT_RED_BLUE, /// mark_missing /// ); @@ -578,15 +581,16 @@ fn mark_selected_chars_in_string( /// assert_eq!(marked_collection, "[1, \u{1b}[34m2\u{1b}[0m, \u{1b}[34m3\u{1b}[0m, 4, \u{1b}[34m5\u{1b}[0m]"); /// # } /// ``` -pub fn mark_selected_items_in_collection( +pub fn mark_selected_items_in_collection( collection: &[T], selected_indices: &HashSet, + representation: &D, format: &DiffFormat, mark: F, ) -> String where - T: Debug, - F: Fn(&T, &DiffFormat) -> String, + D: Represent, + F: Fn(&T, &D, &DiffFormat) -> String, { let mut marked_collection = String::with_capacity(collection.len() + 2); marked_collection.push('['); @@ -595,8 +599,9 @@ where .enumerate() .map(|(index, item)| { if selected_indices.contains(&index) { - mark(item, format) + mark(item, representation, format) } else { + let item = Represented::from((item, representation)); format!("{item:?}") } }) @@ -627,13 +632,17 @@ where /// # fn main() {} /// # #[cfg(feature = "colored")] /// # fn main() { -/// use asserting::colored::{mark_all_items_in_collection, mark_unexpected, DIFF_FORMAT_RED_BLUE}; +/// use asserting::{ +/// colored::{mark_all_items_in_collection, mark_unexpected, DIFF_FORMAT_RED_BLUE}, +/// spec::DebugRepresentation, +/// }; /// use hashbrown::HashSet; /// /// let collection = [1, 2, 3, 4, 5]; /// /// let marked_collection = mark_all_items_in_collection( /// &collection, +/// &DebugRepresentation, /// &DIFF_FORMAT_RED_BLUE, /// mark_unexpected /// ); @@ -641,16 +650,20 @@ where /// assert_eq!(marked_collection, "[\u{1b}[31m1\u{1b}[0m, \u{1b}[31m2\u{1b}[0m, \u{1b}[31m3\u{1b}[0m, \u{1b}[31m4\u{1b}[0m, \u{1b}[31m5\u{1b}[0m]"); /// # } /// ``` -pub fn mark_all_items_in_collection(collection: &[T], format: &DiffFormat, mark: F) -> String +pub fn mark_all_items_in_collection( + collection: &[T], + representation: &D, + format: &DiffFormat, + mark: F, +) -> String where - T: Debug, - F: Fn(&T, &DiffFormat) -> String, + F: Fn(&T, &D, &DiffFormat) -> String, { let mut marked_collection = String::with_capacity(collection.len() + 2); marked_collection.push('['); collection .iter() - .map(|item| mark(item, format)) + .map(|item| mark(item, representation, format)) .for_each(|item| { marked_collection.push_str(&item); marked_collection.push_str(", "); @@ -678,7 +691,8 @@ where /// # fn main() {} /// # #[cfg(all(feature = "colored", feature = "std"))] /// # fn main() { -/// use asserting::colored::{mark_missing_string, mark_selected_entries_in_map, DIFF_FORMAT_RED_BLUE}; +/// use asserting::colored::{mark_missing, mark_selected_entries_in_map, DIFF_FORMAT_RED_BLUE}; +/// use asserting::spec::DebugRepresentation; /// use hashbrown::HashSet; /// use std::collections::BTreeMap; /// @@ -689,23 +703,24 @@ where /// let marked_map = mark_selected_entries_in_map( /// &map_entries, /// &selected_entries, +/// &DebugRepresentation, /// &DIFF_FORMAT_RED_BLUE, -/// mark_missing_string +/// mark_missing, /// ); /// /// assert_eq!(marked_map, "{\u{1b}[34m1: \"one\"\u{1b}[0m, 2: \"two\", \u{1b}[34m3: \"three\"\u{1b}[0m, 4: \"four\"}"); /// # } /// ``` -pub fn mark_selected_entries_in_map( - map_entries: &[(K, V)], +pub fn mark_selected_entries_in_map( + map_entries: &[(&K, &V)], selected_indices: &HashSet, + representation: &D, format: &DiffFormat, mark: F, ) -> String where - K: Debug, - V: Debug, - F: Fn(&str, &DiffFormat) -> String, + F: Fn(&str, &DisplayRepresentation, &DiffFormat) -> String, + D: Represent + Represent, { let mut marked_map_entries = String::with_capacity(map_entries.len() + 2); marked_map_entries.push('{'); @@ -713,9 +728,11 @@ where .iter() .enumerate() .map(|(index, entry)| { - let key_value_pair = format!("{:?}: {:?}", entry.0, entry.1); + let represented_key = Represented::from((entry.0, representation)); + let represented_value = Represented::from((entry.1, representation)); + let key_value_pair = format!("{represented_key:?}: {represented_value:?}"); if selected_indices.contains(&index) { - mark(&key_value_pair, format) + mark(&key_value_pair, &DisplayRepresentation, format) } else { key_value_pair } @@ -747,7 +764,8 @@ where /// # fn main() {} /// # #[cfg(all(feature = "colored", feature = "std"))] /// # fn main() { -/// use asserting::colored::{mark_all_entries_in_map, mark_unexpected_string, DIFF_FORMAT_RED_BLUE}; +/// use asserting::colored::{mark_all_entries_in_map, mark_unexpected, DIFF_FORMAT_RED_BLUE}; +/// use asserting::spec::DebugRepresentation; /// use std::collections::BTreeMap; /// /// let map: BTreeMap<_, _> = [(1, "one"), (2, "two"), (3, "three"), (4, "four")].into(); @@ -755,30 +773,33 @@ where /// let map_entries: Vec<_> = map.iter().collect(); /// let marked_map = mark_all_entries_in_map( /// &map_entries, +/// &DebugRepresentation, /// &DIFF_FORMAT_RED_BLUE, -/// mark_unexpected_string +/// mark_unexpected, /// ); /// /// assert_eq!(marked_map, "{\u{1b}[31m1: \"one\"\u{1b}[0m, \u{1b}[31m2: \"two\"\u{1b}[0m, \u{1b}[31m3: \"three\"\u{1b}[0m, \u{1b}[31m4: \"four\"\u{1b}[0m}"); /// # } /// ``` -pub fn mark_all_entries_in_map( - map_entries: &[(K, V)], +pub fn mark_all_entries_in_map( + map_entries: &[(&K, &V)], + representation: &D, format: &DiffFormat, mark: F, ) -> String where - K: Debug, - V: Debug, - F: Fn(&str, &DiffFormat) -> String, + F: Fn(&str, &DisplayRepresentation, &DiffFormat) -> String, + D: Represent + Represent, { let mut marked_map_entries = String::with_capacity(map_entries.len() + 2); marked_map_entries.push('{'); map_entries .iter() .map(|entry| { - let key_value_pair = format!("{:?}: {:?}", entry.0, entry.1); - mark(&key_value_pair, format) + let represented_key = Represented::from((entry.0, representation)); + let represented_value = Represented::from((entry.1, representation)); + let key_value_pair = format!("{represented_key:?}: {represented_value:?}"); + mark(&key_value_pair, &DisplayRepresentation, format) }) .for_each(|entry| { marked_map_entries.push_str(&entry); @@ -795,9 +816,8 @@ where #[cfg(not(feature = "colored"))] mod without_colored_feature { use super::DIFF_FORMAT_NO_HIGHLIGHT; - use crate::spec::DiffFormat; + use crate::spec::{DiffFormat, Represent, Represented}; use crate::std::{ - fmt::Debug, format, string::{String, ToString}, }; @@ -817,47 +837,31 @@ mod without_colored_feature { } #[inline] - pub fn mark_unexpected_impl(value: &T, _format: &DiffFormat) -> String + pub fn mark_unexpected_impl(value: &T, representation: &D, _format: &DiffFormat) -> String where - T: Debug + ?Sized, + T: ?Sized, + D: Represent, { + let value = Represented::from((value, representation)); format!("{value:?}") } #[inline] - pub fn mark_missing_impl(value: &T, _format: &DiffFormat) -> String + pub fn mark_missing_impl(value: &T, representation: &D, _format: &DiffFormat) -> String where - T: Debug + ?Sized, + T: ?Sized, + D: Represent, { + let value = Represented::from((value, representation)); format!("{value:?}") } - - #[inline] - pub fn mark_unexpected_string_impl(string: &str, _format: &DiffFormat) -> String { - string.to_string() - } - - #[inline] - pub fn mark_missing_string_impl(string: &str, _format: &DiffFormat) -> String { - string.to_string() - } - - #[inline] - pub fn mark_unexpected_char_impl(character: char, _format: &DiffFormat) -> String { - format!("{character}") - } - - #[inline] - pub fn mark_missing_char_impl(character: char, _format: &DiffFormat) -> String { - format!("{character}") - } } #[cfg(feature = "colored")] mod with_colored_feature { use super::DIFF_FORMAT_NO_HIGHLIGHT; - use crate::spec::{DiffFormat, Highlight}; - use crate::std::{fmt::Debug, format, string::String}; + use crate::spec::{DiffFormat, Highlight, Represent, Represented}; + use crate::std::{format, string::String}; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] @@ -1079,48 +1083,26 @@ mod with_colored_feature { } #[inline] - pub fn mark_unexpected_impl(value: &T, format: &DiffFormat) -> String + pub fn mark_unexpected_impl(value: &T, representation: &D, format: &DiffFormat) -> String where - T: Debug + ?Sized, + T: ?Sized, + D: Represent, { + let value = Represented::from((value, representation)); format!( - "{}{value:?}{}", + "{}{value}{}", format.unexpected.start, format.unexpected.end ) } #[inline] - pub fn mark_missing_impl(value: &T, format: &DiffFormat) -> String + pub fn mark_missing_impl(value: &T, representation: &D, format: &DiffFormat) -> String where - T: Debug + ?Sized, + T: ?Sized, + D: Represent, { - format!("{}{value:?}{}", format.missing.start, format.missing.end) - } - - #[inline] - pub fn mark_unexpected_string_impl(string: &str, format: &DiffFormat) -> String { - format!( - "{}{string}{}", - format.unexpected.start, format.unexpected.end - ) - } - - #[inline] - pub fn mark_missing_string_impl(string: &str, format: &DiffFormat) -> String { - format!("{}{string}{}", format.missing.start, format.missing.end) - } - - #[inline] - pub fn mark_unexpected_char_impl(character: char, format: &DiffFormat) -> String { - format!( - "{}{character}{}", - format.unexpected.start, format.unexpected.end - ) - } - - #[inline] - pub fn mark_missing_char_impl(character: char, format: &DiffFormat) -> String { - format!("{}{character}{}", format.missing.start, format.missing.end) + let value = Represented::from((value, representation)); + format!("{}{value}{}", format.missing.start, format.missing.end) } } diff --git a/src/colored/tests.rs b/src/colored/tests.rs index fe1c74f..36aa85b 100644 --- a/src/colored/tests.rs +++ b/src/colored/tests.rs @@ -28,6 +28,7 @@ mod without_colored_feature { #[cfg(feature = "colored")] mod with_colored_feature { use super::*; + use crate::spec::DebugRepresentation; use hashbrown::HashMap; #[test] @@ -72,56 +73,72 @@ mod with_colored_feature { #[test] fn mark_unexpected_highlights_a_string_with_double_quotes() { - let marked_string = mark_unexpected("blandit invidunt", &DIFF_FORMAT_RED_YELLOW); + let marked_string = mark_unexpected( + "blandit invidunt", + &DebugRepresentation, + &DIFF_FORMAT_RED_YELLOW, + ); assert_that(marked_string).is_equal_to("\u{1b}[31m\"blandit invidunt\"\u{1b}[0m"); } #[test] fn mark_missing_highlights_a_string_with_double_quotes() { - let marked_string = mark_missing("blandit invidunt", &DIFF_FORMAT_RED_YELLOW); + let marked_string = mark_missing( + "blandit invidunt", + &DebugRepresentation, + &DIFF_FORMAT_RED_YELLOW, + ); assert_that(marked_string).is_equal_to("\u{1b}[33m\"blandit invidunt\"\u{1b}[0m"); } #[test] fn mark_unexpected_string_highlights_a_string_without_double_quotes() { - let marked_string = mark_unexpected_string("blandit invidunt", &DIFF_FORMAT_RED_YELLOW); + let marked_string = mark_unexpected( + "blandit invidunt", + &DisplayRepresentation, + &DIFF_FORMAT_RED_YELLOW, + ); assert_that(marked_string).is_equal_to("\u{1b}[31mblandit invidunt\u{1b}[0m"); } #[test] fn mark_missing_string_highlights_a_string_without_double_quotes() { - let marked_string = mark_missing_string("blandit invidunt", &DIFF_FORMAT_RED_YELLOW); + let marked_string = mark_missing( + "blandit invidunt", + &DisplayRepresentation, + &DIFF_FORMAT_RED_YELLOW, + ); assert_that(marked_string).is_equal_to("\u{1b}[33mblandit invidunt\u{1b}[0m"); } #[test] fn mark_unexpected_highlights_a_char_with_single_quotes() { - let marked_char = mark_unexpected(&'R', &DIFF_FORMAT_RED_GREEN); + let marked_char = mark_unexpected(&'R', &DebugRepresentation, &DIFF_FORMAT_RED_GREEN); assert_that(marked_char).is_equal_to("\u{1b}[31m'R'\u{1b}[0m"); } #[test] fn mark_missing_highlights_a_char_with_single_quotes() { - let marked_char = mark_missing(&'R', &DIFF_FORMAT_RED_GREEN); + let marked_char = mark_missing(&'R', &DebugRepresentation, &DIFF_FORMAT_RED_GREEN); assert_that(marked_char).is_equal_to("\u{1b}[32m'R'\u{1b}[0m"); } #[test] fn mark_unexpected_char_highlights_char_without_single_quotes() { - let marked_char = mark_unexpected_char('R', &DIFF_FORMAT_RED_GREEN); + let marked_char = mark_unexpected(&'R', &DisplayRepresentation, &DIFF_FORMAT_RED_GREEN); assert_that(marked_char).is_equal_to("\u{1b}[31mR\u{1b}[0m"); } #[test] fn mark_missing_char_highlights_char_without_single_quotes() { - let marked_char = mark_missing_char('R', &DIFF_FORMAT_RED_GREEN); + let marked_char = mark_missing(&'R', &DisplayRepresentation, &DIFF_FORMAT_RED_GREEN); assert_that(marked_char).is_equal_to("\u{1b}[32mR\u{1b}[0m"); } @@ -267,6 +284,7 @@ mod with_colored_feature { let marked_collection = mark_selected_items_in_collection( collection, &selected, + &DebugRepresentation, &DIFF_FORMAT_RED_GREEN, mark_missing, ); @@ -278,8 +296,12 @@ mod with_colored_feature { fn mark_all_items_in_collection_for_empty_collection() { let collection: &[usize] = &[]; - let marked_collection = - mark_all_items_in_collection(collection, &DIFF_FORMAT_RED_GREEN, mark_missing); + let marked_collection = mark_all_items_in_collection( + collection, + &DebugRepresentation, + &DIFF_FORMAT_RED_GREEN, + mark_missing, + ); assert_that(marked_collection).is_equal_to("[]"); } @@ -293,6 +315,7 @@ mod with_colored_feature { let marked_map = mark_selected_entries_in_map( &map_entries, &selected, + &DebugRepresentation, &DIFF_FORMAT_RED_GREEN, mark_missing, ); @@ -300,16 +323,59 @@ mod with_colored_feature { assert_that(marked_map).is_equal_to("{}"); } + #[test] + fn mark_selected_entries_in_map_with_entries() { + let key1 = 1; + let val1 = "one"; + let key2 = 2; + let val2 = "two"; + let map_entries = [(&key1, &val1), (&key2, &val2)]; + let selected: HashSet = [0].into(); + + let marked_map = mark_selected_entries_in_map( + &map_entries, + &selected, + &DebugRepresentation, + &DIFF_FORMAT_RED_BLUE, + mark_missing, + ); + + assert_that(marked_map).is_equal_to("{\u{1b}[34m1: \"one\"\u{1b}[0m, 2: \"two\"}"); + } + #[test] fn mark_all_entries_in_map_for_empty_map() { let map: HashMap = HashMap::new(); let map_entries: Vec<_> = map.iter().collect(); - let marked_map = - mark_all_entries_in_map(&map_entries, &DIFF_FORMAT_RED_GREEN, mark_missing); + let marked_map = mark_all_entries_in_map( + &map_entries, + &DebugRepresentation, + &DIFF_FORMAT_RED_GREEN, + mark_missing, + ); assert_that(marked_map).is_equal_to("{}"); } + + #[test] + fn mark_all_entries_in_map_with_entries() { + let key1 = 1; + let val1 = "one"; + let key2 = 2; + let val2 = "two"; + let map_entries = [(&key1, &val1), (&key2, &val2)]; + + let marked_map = mark_all_entries_in_map( + &map_entries, + &DebugRepresentation, + &DIFF_FORMAT_RED_BLUE, + mark_unexpected, + ); + + assert_that(marked_map) + .is_equal_to("{\u{1b}[31m1: \"one\"\u{1b}[0m, \u{1b}[31m2: \"two\"\u{1b}[0m}"); + } } #[cfg(all(feature = "colored", not(feature = "std")))] diff --git a/src/derived_spec/mod.rs b/src/derived_spec/mod.rs index 2a4f9d6..9579024 100644 --- a/src/derived_spec/mod.rs +++ b/src/derived_spec/mod.rs @@ -35,11 +35,15 @@ use crate::properties::{ MultiplicativeIdentityProperty, SignumProperty, }; use crate::spec::{ - And, AssertFailure, CollectFailures, DiffFormat, DoFail, Expectation, Expecting, Expression, - FailingStrategy, GetFailures, GetLocation, Location, PanicOnFail, Satisfies, SoftPanic, Spec, + AdHocRepresentation, And, AssertFailure, CollectFailures, DebugRepresentation, DiffFormat, + DoFail, Expectation, Expecting, Expression, FailingStrategy, GetFailures, GetLocation, + Location, PanicOnFail, Represent, Represented, RepresentedAs, RepresentedBy, Satisfies, + SoftPanic, Spec, }; use crate::std::borrow::{Cow, ToOwned}; +use crate::std::boxed::Box; use crate::std::error::Error; +use crate::std::fmt; use crate::std::fmt::{Debug, Display}; use crate::std::format; use crate::std::ops::RangeBounds; @@ -59,14 +63,20 @@ use hashbrown::HashSet; /// reports. /// /// [`Spec`]: Spec -pub struct DerivedSpec<'a, O, S> { +pub struct DerivedSpec<'a, O, S, D> { original: O, subject: S, expression: Expression<'a>, diff_format: DiffFormat, + representation: D, } -impl DerivedSpec<'_, O, S> { +impl DerivedSpec<'_, O, S, D> { + /// Returns the subject. + pub fn subject(&self) -> &S { + &self.subject + } + /// Returns the expression (or subject name) if one has been set. pub fn expression(&self) -> &Expression<'_> { &self.expression @@ -76,9 +86,15 @@ impl DerivedSpec<'_, O, S> { pub const fn diff_format(&self) -> &DiffFormat { &self.diff_format } + + /// Returns the representation used for displaying values in failure + /// reports. + pub fn representation(&self) -> &D { + &self.representation + } } -impl<'a, O, S> DerivedSpec<'a, O, S> { +impl<'a, O, S> DerivedSpec<'a, O, S, DebugRepresentation> { #[must_use = "a derived spec does nothing unless an assertion method is called"] pub(crate) fn new( original: O, @@ -91,9 +107,12 @@ impl<'a, O, S> DerivedSpec<'a, O, S> { subject: derived_subject, expression, diff_format, + representation: DebugRepresentation, } } +} +impl<'a, O, S, D> DerivedSpec<'a, O, S, D> { /// Sets the subject name or expression for this assertion. #[must_use = "a derived spec does nothing unless an assertion method is called"] pub fn named(mut self, subject_name: impl Into>) -> Self { @@ -114,7 +133,7 @@ impl<'a, O, S> DerivedSpec<'a, O, S> { } } -impl<'a, O, S> GetLocation<'a> for DerivedSpec<'a, O, S> +impl<'a, O, S, D> GetLocation<'a> for DerivedSpec<'a, O, S, D> where O: GetLocation<'a>, { @@ -123,7 +142,7 @@ where } } -impl GetFailures for DerivedSpec<'_, O, S> +impl GetFailures for DerivedSpec<'_, O, S, D> where O: GetFailures, { @@ -140,7 +159,7 @@ where } } -impl DoFail for DerivedSpec<'_, O, S> +impl DoFail for DerivedSpec<'_, O, S, D> where O: DoFail, { @@ -153,7 +172,7 @@ where } } -impl SoftPanic for DerivedSpec<'_, O, S> +impl SoftPanic for DerivedSpec<'_, O, S, D> where O: SoftPanic, { @@ -162,7 +181,7 @@ where } } -impl And for DerivedSpec<'_, O, S> { +impl And for DerivedSpec<'_, O, S, D> { type Output = O; fn and(self) -> Self::Output { @@ -170,7 +189,7 @@ impl And for DerivedSpec<'_, O, S> { } } -impl<'a, O, S> DerivedSpec<'a, O, S> { +impl<'a, O, S, D> DerivedSpec<'a, O, S, D> { /// Extracts a property from the current subject. /// /// The extracting closure gets a reference to the current subject as an @@ -302,7 +321,7 @@ impl<'a, O, S> DerivedSpec<'a, O, S> { self, property_name: impl Into>, extract: F, - ) -> DerivedSpec<'a, Self, U> + ) -> DerivedSpec<'a, Self, U, DebugRepresentation> where F: FnOnce(&S) -> &B, B: ToOwned + ?Sized, @@ -317,10 +336,10 @@ impl<'a, O, S> DerivedSpec<'a, O, S> { subject: derived_subject, expression, diff_format, + representation: DebugRepresentation, } } - /// Maps the current subject to some other value. /// /// It takes a closure that maps the current subject to a new subject and /// returns a new `DerivedSpec` with the value returned by the closure as @@ -408,7 +427,7 @@ impl<'a, O, S> DerivedSpec<'a, O, S> { self, property_name: impl Into>, extract: F, - ) -> DerivedSpec<'a, O, U> + ) -> DerivedSpec<'a, O, U, DebugRepresentation> where F: FnOnce(S) -> U, { @@ -422,6 +441,7 @@ impl<'a, O, S> DerivedSpec<'a, O, S> { subject: derived_subject, expression, diff_format, + representation: DebugRepresentation, } } @@ -479,7 +499,7 @@ impl<'a, O, S> DerivedSpec<'a, O, S> { /// assertion. So we map the subject of the type `Point` to a tuple of its /// fields. #[must_use = "a derived spec does nothing unless an assertion method is called"] - pub fn mapping(self, map: F) -> DerivedSpec<'a, O, U> + pub fn mapping(self, map: F) -> DerivedSpec<'a, O, U, DebugRepresentation> where F: FnOnce(S) -> U, { @@ -489,37 +509,48 @@ impl<'a, O, S> DerivedSpec<'a, O, S> { subject: mapped, expression: self.expression, diff_format: self.diff_format, + representation: DebugRepresentation, } } } -impl<'a, O, I> DerivedSpec<'a, O, I> +#[allow(clippy::type_complexity)] +impl<'a, O, I, D> DerivedSpec<'a, O, I, D> where I: IntoIterator, + D: Clone, { pub(crate) fn extracting_ref_iter( self, property_name: impl Into>, extract: F, - ) -> DerivedSpec<'a, DerivedSpec<'a, O, Vec<::Item>>, Vec> + ) -> DerivedSpec< + 'a, + DerivedSpec<'a, O, Vec<::Item>, D>, + Vec, + DebugRepresentation, + > where for<'b> F: Fn(slice::Iter<'b, ::Item>) -> Vec, { let property_name = Expression(property_name.into()); let diff_format = self.diff_format.clone(); - let orig_spec = self.mapping(Vec::from_iter); + let representation = self.representation.clone(); + let orig_spec = self.mapping(Vec::from_iter).represented_by(representation); let new_subject = extract(orig_spec.subject.iter()); DerivedSpec { original: orig_spec, subject: new_subject, expression: property_name, diff_format, + representation: DebugRepresentation, } } } -impl Satisfies for DerivedSpec<'_, O, S> +impl Satisfies for DerivedSpec<'_, O, S, D> where + D: Represent, O: DoFail, { fn satisfies

(self, predicate: P) -> Self @@ -537,25 +568,56 @@ where } } -impl Expecting for DerivedSpec<'_, O, S> +impl Expecting for DerivedSpec<'_, O, S, D> where O: DoFail, { - fn expecting(mut self, mut expectation: impl Expectation) -> Self { + fn expecting(mut self, mut expectation: impl Expectation) -> Self { if !expectation.test(&self.subject) { - let message = - expectation.message(&self.expression, &self.subject, false, &self.diff_format); + let message = expectation.message( + &self.expression, + &self.subject, + false, + &self.representation, + &self.diff_format, + ); self.do_fail_with_message(message); } self } } -impl AssertEquality for DerivedSpec<'_, O, S> +impl<'a, O, S, D, D2> RepresentedBy for DerivedSpec<'a, O, S, D> { + type Output = DerivedSpec<'a, O, S, D2>; + + fn represented_by(self, representation: D2) -> Self::Output { + DerivedSpec { + original: self.original, + subject: self.subject, + expression: self.expression, + diff_format: self.diff_format, + representation, + } + } +} + +impl<'a, O, S, D> RepresentedAs for DerivedSpec<'a, O, S, D> { + type Subject = S; + type Output = DerivedSpec<'a, O, S, AdHocRepresentation>; + + fn represented_as(self, representation: F) -> Self::Output + where + F: Fn(&S, &mut fmt::Formatter<'_>) -> fmt::Result + 'static, + { + self.represented_by(AdHocRepresentation(Box::new(representation))) + } +} + +impl AssertEquality for DerivedSpec<'_, O, S, D> where - S: PartialEq + Debug, - E: Debug, + S: PartialEq, O: DoFail, + D: Represent + Represent, { fn is_equal_to(self, expected: E) -> Self { self.expecting(is_equal_to(expected)) @@ -566,10 +628,11 @@ where } } -impl AssertSameAs for DerivedSpec<'_, O, S> +impl AssertSameAs for DerivedSpec<'_, O, S, D> where - S: PartialEq + Debug, + S: PartialEq, O: DoFail, + D: Represent, { fn is_same_as(self, expected: S) -> Self { self.expecting(is_same_as(expected)) @@ -585,11 +648,12 @@ mod float_cmp { use super::DerivedSpec; use crate::assertions::{AssertIsCloseToWithDefaultMargin, AssertIsCloseToWithinMargin}; use crate::expectations::{is_close_to, not}; - use crate::spec::{DoFail, Expecting}; + use crate::spec::{DoFail, Expecting, Represent}; use float_cmp::{F32Margin, F64Margin}; - impl AssertIsCloseToWithinMargin for DerivedSpec<'_, O, f32> + impl AssertIsCloseToWithinMargin for DerivedSpec<'_, O, f32, D> where + D: Represent, O: DoFail, { fn is_close_to_with_margin(self, expected: f32, margin: impl Into) -> Self { @@ -601,8 +665,9 @@ mod float_cmp { } } - impl AssertIsCloseToWithDefaultMargin for DerivedSpec<'_, O, f32> + impl AssertIsCloseToWithDefaultMargin for DerivedSpec<'_, O, f32, D> where + D: Represent, O: DoFail, { fn is_close_to(self, expected: f32) -> Self { @@ -616,8 +681,9 @@ mod float_cmp { } } - impl AssertIsCloseToWithinMargin for DerivedSpec<'_, O, f64> + impl AssertIsCloseToWithinMargin for DerivedSpec<'_, O, f64, D> where + D: Represent, O: DoFail, { fn is_close_to_with_margin(self, expected: f64, margin: impl Into) -> Self { @@ -629,8 +695,9 @@ mod float_cmp { } } - impl AssertIsCloseToWithDefaultMargin for DerivedSpec<'_, O, f64> + impl AssertIsCloseToWithDefaultMargin for DerivedSpec<'_, O, f64, D> where + D: Represent, O: DoFail, { fn is_close_to(self, expected: f64) -> Self { @@ -645,10 +712,10 @@ mod float_cmp { } } -impl AssertOrder for DerivedSpec<'_, O, S> +impl AssertOrder for DerivedSpec<'_, O, S, D> where - S: PartialOrd + Debug, - E: Debug, + S: PartialOrd, + D: Represent + Represent, O: DoFail, { fn is_less_than(self, expected: E) -> Self { @@ -680,30 +747,34 @@ where } } -impl AssertInRange for DerivedSpec<'_, O, S> +impl AssertInRange for DerivedSpec<'_, O, S, D> where - S: PartialOrd + Debug, - E: PartialOrd + Debug, + S: PartialOrd, + E: PartialOrd, + D: Represent + Represent, O: DoFail, { fn is_in_range(self, range: R) -> Self where - R: RangeBounds + Debug, + R: RangeBounds, + D: Represent, { self.expecting(is_in_range(range)) } fn is_not_in_range(self, range: R) -> Self where - R: RangeBounds + Debug, + R: RangeBounds, + D: Represent, { self.expecting(not(is_in_range(range))) } } -impl AssertNumericIdentity for DerivedSpec<'_, O, S> +impl AssertNumericIdentity for DerivedSpec<'_, O, S, D> where - S: AdditiveIdentityProperty + MultiplicativeIdentityProperty + PartialEq + Debug, + S: AdditiveIdentityProperty + MultiplicativeIdentityProperty + PartialEq, + D: Represent, O: DoFail, { fn is_zero(self) -> Self { @@ -715,9 +786,10 @@ where } } -impl AssertSignum for DerivedSpec<'_, O, S> +impl AssertSignum for DerivedSpec<'_, O, S, D> where - S: SignumProperty + Debug, + S: SignumProperty, + D: Represent, O: DoFail, { fn is_negative(self) -> Self { @@ -737,9 +809,10 @@ where } } -impl AssertInfinity for DerivedSpec<'_, O, S> +impl AssertInfinity for DerivedSpec<'_, O, S, D> where - S: InfinityProperty + Debug, + S: InfinityProperty, + D: Represent, O: DoFail, { fn is_infinite(self) -> Self { @@ -751,9 +824,10 @@ where } } -impl AssertNotANumber for DerivedSpec<'_, O, S> +impl AssertNotANumber for DerivedSpec<'_, O, S, D> where - S: IsNanProperty + Debug, + S: IsNanProperty, + D: Represent, O: DoFail, { fn is_not_a_number(self) -> Self { @@ -765,9 +839,10 @@ where } } -impl AssertDecimalNumber for DerivedSpec<'_, O, S> +impl AssertDecimalNumber for DerivedSpec<'_, O, S, D> where - S: DecimalProperties + Debug, + S: DecimalProperties, + D: Represent + Represent + Represent, O: DoFail, { fn has_scale_of(self, expected_scale: i64) -> Self { @@ -783,8 +858,9 @@ where } } -impl AssertBoolean for DerivedSpec<'_, O, bool> +impl AssertBoolean for DerivedSpec<'_, O, bool, D> where + D: Represent, O: DoFail, { fn is_true(self) -> Self { @@ -796,8 +872,9 @@ where } } -impl AssertChar for DerivedSpec<'_, O, char> +impl AssertChar for DerivedSpec<'_, O, char, D> where + D: Represent + Represent, O: DoFail, { fn is_lowercase(self) -> Self { @@ -833,8 +910,9 @@ where } } -impl AssertChar for DerivedSpec<'_, O, &char> +impl AssertChar for DerivedSpec<'_, O, &char, D> where + D: Represent + Represent, O: DoFail, { fn is_lowercase(self) -> Self { @@ -870,9 +948,10 @@ where } } -impl AssertEmptiness for DerivedSpec<'_, O, S> +impl AssertEmptiness for DerivedSpec<'_, O, S, D> where - S: IsEmptyProperty + Debug, + S: IsEmptyProperty, + D: Represent, O: DoFail, { fn is_empty(self) -> Self { @@ -884,9 +963,10 @@ where } } -impl AssertHasLength for DerivedSpec<'_, O, S> +impl AssertHasLength for DerivedSpec<'_, O, S, D> where - S: LengthProperty + Debug, + S: LengthProperty, + D: Represent, O: DoFail, { fn has_length(self, expected_length: usize) -> Self { @@ -895,7 +975,8 @@ where fn has_length_in_range(self, expected_range: R) -> Self where - R: RangeBounds + Debug, + R: RangeBounds, + D: Represent, { self.expecting(has_length_in_range(expected_range)) } @@ -917,9 +998,10 @@ where } } -impl AssertHasCharCount for DerivedSpec<'_, O, S> +impl AssertHasCharCount for DerivedSpec<'_, O, S, D> where - S: CharCountProperty + Debug, + S: CharCountProperty, + D: Represent, O: DoFail, { fn has_char_count(self, expected_char_count: usize) -> Self { @@ -928,7 +1010,8 @@ where fn has_char_count_in_range(self, expected_range: U) -> Self where - U: RangeBounds + Debug, + U: RangeBounds, + D: Represent, { self.expecting(has_char_count_in_range(expected_range)) } @@ -950,9 +1033,9 @@ where } } -impl AssertOption for DerivedSpec<'_, O, Option> +impl AssertOption for DerivedSpec<'_, O, Option, D> where - S: Debug, + D: Represent, O: DoFail, { fn is_some(self) -> Self { @@ -964,11 +1047,11 @@ where } } -impl<'a, O, T> AssertOptionValue for DerivedSpec<'a, O, Option> +impl<'a, O, T, D> AssertOptionValue for DerivedSpec<'a, O, Option, D> where O: DoFail, { - type Some = DerivedSpec<'a, O, T>; + type Some = DerivedSpec<'a, O, T, DebugRepresentation>; fn some(self) -> Self::Some { self.mapping(|subject| match subject { @@ -980,12 +1063,12 @@ where } } -impl<'a, O, T> AssertOptionValue for DerivedSpec<'a, O, &'a Option> +impl<'a, O, T, D> AssertOptionValue for DerivedSpec<'a, O, &'a Option, D> where T: 'a, O: DoFail, { - type Some = DerivedSpec<'a, O, &'a T>; + type Some = DerivedSpec<'a, O, &'a T, DebugRepresentation>; fn some(self) -> Self::Some { self.mapping(|subject| match subject { @@ -997,10 +1080,10 @@ where } } -impl AssertHasValue for DerivedSpec<'_, O, Option> +impl AssertHasValue for DerivedSpec<'_, O, Option, D> where - T: PartialEq + Debug, - E: Debug, + T: PartialEq, + D: Represent + Represent, O: DoFail, { fn has_value(self, expected: E) -> Self { @@ -1008,10 +1091,10 @@ where } } -impl AssertHasValue for DerivedSpec<'_, O, &Option> +impl AssertHasValue for DerivedSpec<'_, O, &Option, D> where - T: PartialEq + Debug, - E: Debug, + T: PartialEq, + D: Represent + Represent, O: DoFail, { fn has_value(self, expected: E) -> Self { @@ -1019,10 +1102,9 @@ where } } -impl AssertResult for DerivedSpec<'_, O, Result> +impl AssertResult for DerivedSpec<'_, O, Result, D> where - T: Debug, - E: Debug, + D: Represent + Represent, O: DoFail, { fn is_ok(self) -> Self { @@ -1034,10 +1116,9 @@ where } } -impl AssertResult for DerivedSpec<'_, O, &Result> +impl AssertResult for DerivedSpec<'_, O, &Result, D> where - T: Debug, - E: Debug, + D: Represent + Represent, O: DoFail, { fn is_ok(self) -> Self { @@ -1049,67 +1130,76 @@ where } } -impl<'a, O, T, E> AssertResultValue for DerivedSpec<'a, O, Result> +impl<'a, O, T, E, D> AssertResultValue for DerivedSpec<'a, O, Result, D> where - T: Debug, - E: Debug, + D: Represent + Represent + Clone, O: DoFail, { - type Ok = DerivedSpec<'a, O, T>; - type Err = DerivedSpec<'a, O, E>; + type Ok = DerivedSpec<'a, O, T, D>; + type Err = DerivedSpec<'a, O, E, D>; fn ok(self) -> Self::Ok { + let representation = self.representation().clone(); self.mapping(|subject| match subject { Ok(value) => value, Err(error) => { + let error = Represented::from((&error, &representation)); panic!("expected the subject to be `Ok(_)`, but was `Err({error:?})`") }, }) + .represented_by(representation) } fn err(self) -> Self::Err { + let representation = self.representation().clone(); self.mapping(|subject| match subject { Ok(value) => { + let value = Represented::from((&value, &representation)); panic!("expected the subject to be `Err(_)`, but was `Ok({value:?})`") }, Err(error) => error, }) + .represented_by(representation) } } -impl<'a, O, T, E> AssertResultValue for DerivedSpec<'a, O, &'a Result> +impl<'a, O, T, E, D> AssertResultValue for DerivedSpec<'a, O, &'a Result, D> where - T: Debug, - E: Debug, + D: Represent + Represent + Clone, O: DoFail, { - type Ok = DerivedSpec<'a, O, &'a T>; - type Err = DerivedSpec<'a, O, &'a E>; + type Ok = DerivedSpec<'a, O, &'a T, D>; + type Err = DerivedSpec<'a, O, &'a E, D>; fn ok(self) -> Self::Ok { + let representation = self.representation().clone(); self.mapping(|subject| match subject { Ok(value) => value, Err(error) => { + let error = Represented::from((error, &representation)); panic!("expected the subject to be `Ok(_)`, but was `Err({error:?})`") }, }) + .represented_by(representation) } fn err(self) -> Self::Err { + let representation = self.representation().clone(); self.mapping(|subject| match subject { Ok(value) => { + let value = Represented::from((value, &representation)); panic!("expected the subject to be `Err(_)`, but was `Ok({value:?})`") }, Err(error) => error, }) + .represented_by(representation) } } -impl AssertHasValue for DerivedSpec<'_, O, Result> +impl AssertHasValue for DerivedSpec<'_, O, Result, D> where - T: PartialEq + Debug, - E: Debug, - X: Debug, + T: PartialEq, + D: Represent + Represent + Represent, O: DoFail, { fn has_value(self, expected: X) -> Self { @@ -1117,11 +1207,10 @@ where } } -impl AssertHasValue for DerivedSpec<'_, O, &Result> +impl AssertHasValue for DerivedSpec<'_, O, &Result, D> where - T: PartialEq + Debug, - E: Debug, - X: Debug, + T: PartialEq, + D: Represent + Represent + Represent, O: DoFail, { fn has_value(self, expected: X) -> Self { @@ -1129,11 +1218,10 @@ where } } -impl AssertHasError for DerivedSpec<'_, O, Result> +impl AssertHasError for DerivedSpec<'_, O, Result, D> where - T: Debug, - E: PartialEq + Debug, - X: Debug, + E: PartialEq, + D: Represent + Represent + Represent, O: DoFail, { fn has_error(self, expected: X) -> Self { @@ -1141,11 +1229,10 @@ where } } -impl AssertHasError for DerivedSpec<'_, O, &Result> +impl AssertHasError for DerivedSpec<'_, O, &Result, D> where - T: Debug, - E: PartialEq + Debug, - X: Debug, + E: PartialEq, + D: Represent + Represent + Represent, O: DoFail, { fn has_error(self, expected: X) -> Self { @@ -1153,48 +1240,69 @@ where } } -impl<'a, O, T, E, X> AssertHasErrorMessage for DerivedSpec<'a, O, Result> +impl<'a, O, T, E, X, D> AssertHasErrorMessage for DerivedSpec<'a, O, Result, D> where - T: Debug, E: Display, X: Debug, String: PartialEq, + D: Represent, O: DoFail, { - type ErrorMessage = DerivedSpec<'a, O, String>; + type ErrorMessage = DerivedSpec<'a, O, String, DebugRepresentation>; fn has_error_message(self, expected: X) -> Self::ErrorMessage { - self.mapping(|result| match result { - Ok(value) => panic!("expected the subject to be `Err(_)` with message {expected:?}, but was `Ok({value:?})`"), - Err(error) => error.to_string(), - }).expecting(is_equal_to(expected)) + let subject = match self.subject() { + Ok(value) => Ok(format!( + "Ok({:?})", + Represented::from((value, self.representation())) + )), + Err(error) => Err(error.to_string()), + }; + self.mapping(|_result| match subject { + Ok(value) => panic!( + "expected the subject to be `Err(_)` with message {expected:?}, but was `{value}`" + ), + Err(error) => error, + }) + .expecting(is_equal_to(expected)) } } -impl<'a, O, T, E, X> AssertHasErrorMessage for DerivedSpec<'a, O, &Result> +impl<'a, O, T, E, X, D> AssertHasErrorMessage for DerivedSpec<'a, O, &Result, D> where - T: Debug, E: Display, X: Debug, String: PartialEq, + D: Represent, O: DoFail, { - type ErrorMessage = DerivedSpec<'a, O, String>; + type ErrorMessage = DerivedSpec<'a, O, String, DebugRepresentation>; fn has_error_message(self, expected: X) -> Self::ErrorMessage { - self.mapping(|result| match result { - Ok(value) => panic!("expected the subject to be `Err(_)` with message {expected:?}, but was `Ok({value:?})`"), - Err(error) => error.to_string(), - }).expecting(is_equal_to(expected)) + let subject = match self.subject() { + Ok(value) => Ok(format!( + "Ok({:?})", + Represented::from((value, self.representation())) + )), + Err(error) => Err(error.to_string()), + }; + self.mapping(|_result| match subject { + Ok(value) => panic!( + "expected the subject to be `Err(_)` with message {expected:?}, but was `{value}`" + ), + Err(error) => error, + }) + .expecting(is_equal_to(expected)) } } -impl<'a, O, S> AssertErrorHasSource for DerivedSpec<'a, O, S> +impl<'a, O, S, D> AssertErrorHasSource for DerivedSpec<'a, O, S, D> where S: Error, + D: Represent, O: DoFail, { - type SourceMessage = DerivedSpec<'a, O, Option>; + type SourceMessage = DerivedSpec<'a, O, Option, DebugRepresentation>; fn has_no_source(self) -> Self { self.expecting(not(error_has_source())) @@ -1211,7 +1319,7 @@ where } } -impl AssertHasDebugString for DerivedSpec<'_, O, S> +impl AssertHasDebugString for DerivedSpec<'_, O, S, D> where S: Debug, E: AsRef, @@ -1226,12 +1334,12 @@ where } } -impl<'a, O, S> AssertDebugString for DerivedSpec<'a, O, S> +impl<'a, O, S, D> AssertDebugString for DerivedSpec<'a, O, S, D> where S: Debug, O: DoFail, { - type DebugString = DerivedSpec<'a, O, String>; + type DebugString = DerivedSpec<'a, O, String, DebugRepresentation>; fn debug_string(self) -> Self::DebugString { let expression_debug_string = format!("{}'s debug string", self.expression); @@ -1240,7 +1348,7 @@ where } } -impl AssertHasDisplayString for DerivedSpec<'_, O, S> +impl AssertHasDisplayString for DerivedSpec<'_, O, S, D> where S: Display, E: AsRef, @@ -1255,12 +1363,12 @@ where } } -impl<'a, O, S> AssertDisplayString for DerivedSpec<'a, O, S> +impl<'a, O, S, D> AssertDisplayString for DerivedSpec<'a, O, S, D> where S: Display, O: DoFail, { - type DisplayString = DerivedSpec<'a, O, String>; + type DisplayString = DerivedSpec<'a, O, String, DebugRepresentation>; fn display_string(self) -> Self::DisplayString { let expression_display_string = format!("{}'s display string", self.expression); @@ -1269,9 +1377,10 @@ where } } -impl<'a, O, S> AssertStringPattern<&'a str> for DerivedSpec<'a, O, S> +impl<'a, O, S, D> AssertStringPattern<&'a str> for DerivedSpec<'a, O, S, D> where - S: 'a + AsRef + Debug, + S: 'a + AsRef, + D: Represent, O: DoFail, { fn contains(self, pattern: &'a str) -> Self { @@ -1299,9 +1408,10 @@ where } } -impl<'a, O, S> AssertStringPattern for DerivedSpec<'a, O, S> +impl<'a, O, S, D> AssertStringPattern for DerivedSpec<'a, O, S, D> where - S: 'a + AsRef + Debug, + S: 'a + AsRef, + D: Represent, O: DoFail, { fn contains(self, pattern: String) -> Self { @@ -1329,9 +1439,10 @@ where } } -impl<'a, O, S> AssertStringPattern for DerivedSpec<'a, O, S> +impl<'a, O, S, D> AssertStringPattern for DerivedSpec<'a, O, S, D> where - S: 'a + AsRef + Debug, + S: 'a + AsRef, + D: Represent + Represent, O: DoFail, { fn contains(self, pattern: char) -> Self { @@ -1359,9 +1470,10 @@ where } } -impl<'a, O, S> AssertStringContainsAnyOf<&'a [char]> for DerivedSpec<'a, O, S> +impl<'a, O, S, D> AssertStringContainsAnyOf<&'a [char]> for DerivedSpec<'a, O, S, D> where - S: 'a + AsRef + Debug, + S: 'a + AsRef, + D: Represent + Represent, O: DoFail, { fn contains_any_of(self, expected: &'a [char]) -> Self { @@ -1373,9 +1485,10 @@ where } } -impl<'a, O, S, const N: usize> AssertStringContainsAnyOf<[char; N]> for DerivedSpec<'a, O, S> +impl<'a, O, S, const N: usize, D> AssertStringContainsAnyOf<[char; N]> for DerivedSpec<'a, O, S, D> where - S: 'a + AsRef + Debug, + S: 'a + AsRef, + D: Represent + Represent, O: DoFail, { fn contains_any_of(self, expected: [char; N]) -> Self { @@ -1387,9 +1500,11 @@ where } } -impl<'a, O, S, const N: usize> AssertStringContainsAnyOf<&'a [char; N]> for DerivedSpec<'a, O, S> +impl<'a, O, S, const N: usize, D> AssertStringContainsAnyOf<&'a [char; N]> + for DerivedSpec<'a, O, S, D> where - S: 'a + AsRef + Debug, + S: 'a + AsRef, + D: Represent + Represent, O: DoFail, { fn contains_any_of(self, expected: &'a [char; N]) -> Self { @@ -1406,12 +1521,12 @@ mod regex { use crate::assertions::AssertStringMatches; use crate::derived_spec::DerivedSpec; use crate::expectations::{not, string_matches}; - use crate::spec::{DoFail, Expecting}; - use crate::std::fmt::Debug; + use crate::spec::{DoFail, Expecting, Represent}; - impl AssertStringMatches for DerivedSpec<'_, O, S> + impl AssertStringMatches for DerivedSpec<'_, O, S, D> where - S: AsRef + Debug, + S: AsRef, + D: Represent, O: DoFail, { fn matches(self, regex_pattern: &str) -> Self { @@ -1424,111 +1539,139 @@ mod regex { } } -impl<'a, O, S, T, E> AssertIteratorContains for DerivedSpec<'a, O, S> +impl<'a, O, S, T, E, D> AssertIteratorContains for DerivedSpec<'a, O, S, D> where S: IntoIterator, - T: PartialEq + Debug, - E: Debug, + T: PartialEq, + D: Represent + Represent + Clone, O: DoFail, { - type Sequence = DerivedSpec<'a, O, Vec>; + type Sequence = DerivedSpec<'a, O, Vec, D>; fn contains(self, element: E) -> Self::Sequence { + let representation = self.representation().clone(); self.mapping(Vec::from_iter) + .represented_by(representation) .expecting(iterator_contains(element)) } fn does_not_contain(self, element: E) -> Self::Sequence { + let representation = self.representation().clone(); self.mapping(Vec::from_iter) + .represented_by(representation) .expecting(not(iterator_contains(element))) } } -impl<'a, O, S, T, E> AssertIteratorContainsInAnyOrder for DerivedSpec<'a, O, S> +impl<'a, O, S, T, E, D> AssertIteratorContainsInAnyOrder for DerivedSpec<'a, O, S, D> where S: IntoIterator, - T: PartialEq<::Item> + Debug, + T: PartialEq<::Item>, E: IntoIterator, - ::Item: Debug, + D: Represent + Represent<::Item> + Clone, O: DoFail, { - type Sequence = DerivedSpec<'a, O, Vec>; + type Sequence = DerivedSpec<'a, O, Vec, D>; fn contains_exactly_in_any_order(self, expected: E) -> Self::Sequence { + let representation = self.representation().clone(); self.mapping(Vec::from_iter) + .represented_by(representation) .expecting(iterator_contains_exactly_in_any_order(expected)) } fn contains_any_of(self, expected: E) -> Self::Sequence { + let representation = self.representation().clone(); self.mapping(Vec::from_iter) + .represented_by(representation) .expecting(iterator_contains_any_of(expected)) } fn does_not_contain_any_of(self, expected: E) -> Self::Sequence { + let representation = self.representation().clone(); self.mapping(Vec::from_iter) + .represented_by(representation) .expecting(not(iterator_contains_any_of(expected))) } fn contains_all_of(self, expected: E) -> Self::Sequence { + let representation = self.representation().clone(); self.mapping(Vec::from_iter) + .represented_by(representation) .expecting(iterator_contains_all_of(expected)) } fn contains_only(self, expected: E) -> Self::Sequence { + let representation = self.representation().clone(); self.mapping(Vec::from_iter) + .represented_by(representation) .expecting(iterator_contains_only(expected)) } fn contains_only_once(self, expected: E) -> Self::Sequence { + let representation = self.representation().clone(); self.mapping(Vec::from_iter) + .represented_by(representation) .expecting(iterator_contains_only_once(expected)) } } -impl<'a, O, S, T, E> AssertIteratorContainsInOrder for DerivedSpec<'a, O, S> +impl<'a, O, S, T, E, D> AssertIteratorContainsInOrder for DerivedSpec<'a, O, S, D> where S: IntoIterator, ::IntoIter: DefinedOrderProperty, E: IntoIterator, ::IntoIter: DefinedOrderProperty, - ::Item: Debug, - T: PartialEq<::Item> + Debug, + T: PartialEq<::Item>, + D: Represent + Represent<::Item> + Clone, O: DoFail, { - type Sequence = DerivedSpec<'a, O, Vec>; + type Sequence = DerivedSpec<'a, O, Vec, D>; fn contains_exactly(self, expected: E) -> Self::Sequence { + let representation = self.representation().clone(); self.mapping(Vec::from_iter) + .represented_by(representation) .expecting(iterator_contains_exactly(expected)) } fn contains_sequence(self, expected: E) -> Self::Sequence { + let representation = self.representation().clone(); self.mapping(Vec::from_iter) + .represented_by(representation) .expecting(iterator_contains_sequence(expected)) } fn contains_all_in_order(self, expected: E) -> Self::Sequence { + let representation = self.representation().clone(); self.mapping(Vec::from_iter) + .represented_by(representation) .expecting(iterator_contains_all_in_order(expected)) } fn starts_with(self, expected: E) -> Self::Sequence { + let representation = self.representation().clone(); self.mapping(Vec::from_iter) + .represented_by(representation) .expecting(iterator_starts_with(expected)) } fn ends_with(self, expected: E) -> Self::Sequence { + let representation = self.representation().clone(); self.mapping(Vec::from_iter) + .represented_by(representation) .expecting(iterator_ends_with(expected)) } } -impl AssertMapContainsKey for DerivedSpec<'_, O, S> +impl AssertMapContainsKey for DerivedSpec<'_, O, S, D> where - S: MapProperties + Debug, - ::Key: PartialEq + Debug, - ::Value: Debug, - E: Debug, + S: MapProperties, + ::Key: PartialEq, + D: Represent + + Represent + + Represent<::Key> + + Represent<::Value>, O: DoFail, { fn contains_key(self, expected_key: E) -> Self { @@ -1552,12 +1695,14 @@ where } } -impl AssertMapContainsValue for DerivedSpec<'_, O, S> +impl AssertMapContainsValue for DerivedSpec<'_, O, S, D> where - S: MapProperties + Debug, - ::Key: Debug, - ::Value: PartialEq + Debug, - E: Debug, + S: MapProperties, + ::Value: PartialEq, + D: Represent + + Represent + + Represent<::Key> + + Represent<::Value>, O: DoFail, { fn contains_value(self, expected_value: E) -> Self { @@ -1577,19 +1722,21 @@ where } } -impl<'a, O, S, T> AssertOrderedElements for DerivedSpec<'a, O, S> +impl<'a, O, S, T, D> AssertOrderedElements for DerivedSpec<'a, O, S, D> where S: IntoIterator, ::IntoIter: DefinedOrderProperty, - T: Debug, + D: Represent + Clone, O: DoFail + GetFailures, { - type SingleElement = DerivedSpec<'a, O, T>; - type MultipleElements = DerivedSpec<'a, O, Vec>; + type SingleElement = DerivedSpec<'a, O, T, D>; + type MultipleElements = DerivedSpec<'a, O, Vec, D>; fn first_element(self) -> Self::SingleElement { + let representation = self.representation().clone(); let spec = self .mapping(Vec::from_iter) + .represented_by(representation.clone()) .expecting(has_at_least_number_of_elements(1)); if spec.has_failures() { PanicOnFail.do_fail_with(&spec.failures()); @@ -1599,11 +1746,14 @@ where let new_subject_name = format!("the first element of {orig_subject_name}"); spec.extracting("", |mut collection| collection.remove(0)) .named(new_subject_name) + .represented_by(representation) } fn last_element(self) -> Self::SingleElement { + let representation = self.representation().clone(); let spec = self .mapping(Vec::from_iter) + .represented_by(representation.clone()) .expecting(has_at_least_number_of_elements(1)); if spec.has_failures() { PanicOnFail.do_fail_with(&spec.failures()); @@ -1617,12 +1767,15 @@ where }) }) .named(new_subject_name) + .represented_by(representation) } fn nth_element(self, n: usize) -> Self::SingleElement { + let representation = self.representation().clone(); let min_len = n + 1; let spec = self .mapping(Vec::from_iter) + .represented_by(representation.clone()) .expecting(has_at_least_number_of_elements(min_len)); if spec.has_failures() { PanicOnFail.do_fail_with(&spec.failures()); @@ -1632,9 +1785,11 @@ where let new_subject_name = format!("{orig_subject_name}[{n}]"); spec.extracting("", |mut collection| collection.remove(n)) .named(new_subject_name) + .represented_by(representation) } fn elements_at(self, indices: impl IntoIterator) -> Self::MultipleElements { + let representation = self.representation().clone(); let indices = Vec::from_iter(indices); let orig_subject_name = self.expression(); let new_subject_name = format!("{orig_subject_name} at positions {indices:?}"); @@ -1647,19 +1802,20 @@ where .collect() }) .named(new_subject_name) + .represented_by(representation) } } -impl<'a, O, I> AssertElements<'a, I> for DerivedSpec<'a, O, I> +impl<'a, O, I, D> AssertElements<'a, I> for DerivedSpec<'a, O, I, D> where I: 'a + IntoIterator, O: DoFail + GetLocation<'a>, { - type Output = DerivedSpec<'a, O, ()>; + type Output = DerivedSpec<'a, O, (), DebugRepresentation>; fn each_element(mut self, assert: A) -> Self::Output where - A: Fn(Spec<'a, ::Item, CollectFailures>) -> B, + A: Fn(Spec<'a, ::Item, DebugRepresentation, CollectFailures>) -> B, B: GetFailures, { let root_expression = &self.expression; @@ -1686,12 +1842,13 @@ where subject: (), expression: self.expression, diff_format: self.diff_format, + representation: DebugRepresentation, } } fn any_element(mut self, assert: A) -> Self::Output where - A: Fn(Spec<'a, ::Item, CollectFailures>) -> B, + A: Fn(Spec<'a, ::Item, DebugRepresentation, CollectFailures>) -> B, B: GetFailures, { let root_expression = &self.expression; @@ -1723,23 +1880,27 @@ where subject: (), expression: self.expression, diff_format: self.diff_format, + representation: DebugRepresentation, } } } -impl<'a, O, S, T, U> AssertOrderedElementsRef for DerivedSpec<'a, O, S> +impl<'a, O, S, T, U, D> AssertOrderedElementsRef for DerivedSpec<'a, O, S, D> where S: IntoIterator, ::IntoIter: DefinedOrderProperty, - T: ToOwned + Debug, + T: ToOwned, + D: Represent + Clone, O: DoFail + GetFailures, { - type SingleElement = DerivedSpec<'a, DerivedSpec<'a, O, Vec>, U>; - type MultipleElements = DerivedSpec<'a, DerivedSpec<'a, O, Vec>, Vec>; + type SingleElement = DerivedSpec<'a, DerivedSpec<'a, O, Vec, D>, U, D>; + type MultipleElements = DerivedSpec<'a, DerivedSpec<'a, O, Vec, D>, Vec, D>; fn first_element_ref(self) -> Self::SingleElement { + let representation = self.representation().clone(); let original_spec = self .mapping(Vec::from_iter) + .represented_by(representation.clone()) .expecting(has_at_least_number_of_elements(1)); if original_spec.has_failures() { PanicOnFail.do_fail_with(&original_spec.failures()); @@ -1753,11 +1914,14 @@ where unreachable!("We should have asserted before, that there is at least one element in the collection/iterator. Please file a bug.") ) ).named(new_subject_name) + .represented_by(representation) } fn last_element_ref(self) -> Self::SingleElement { + let representation = self.representation().clone(); let original_spec = self .mapping(Vec::from_iter) + .represented_by(representation.clone()) .expecting(has_at_least_number_of_elements(1)); if original_spec.has_failures() { PanicOnFail.do_fail_with(&original_spec.failures()); @@ -1771,12 +1935,15 @@ where unreachable!("We should have asserted before, that there is at least one element in the collection/iterator. Please file a bug.") ) ).named(new_subject_name) + .represented_by(representation) } fn nth_element_ref(self, n: usize) -> Self::SingleElement { + let representation = self.representation().clone(); let min_len = n + 1; let original_spec = self .mapping(Vec::from_iter) + .represented_by(representation.clone()) .expecting(has_at_least_number_of_elements(min_len)); if original_spec.has_failures() { PanicOnFail.do_fail_with(&original_spec.failures()); @@ -1790,14 +1957,18 @@ where unreachable!("We should have asserted before, that there is at least one element in the collection/iterator. Please file a bug.") ) ).named(new_subject_name) + .represented_by(representation) } fn elements_ref_at(self, indices: impl IntoIterator) -> Self::MultipleElements { + let representation = self.representation().clone(); let indices = Vec::from_iter(indices); let orig_subject_name = self.expression(); let new_subject_name = format!("{orig_subject_name} at positions {indices:?}"); let indices = HashSet::<_>::from_iter(indices); - let original_spec = self.mapping(Vec::from_iter); + let original_spec = self + .mapping(Vec::from_iter) + .represented_by(representation.clone()); original_spec .extracting_ref_iter("", |collection| { collection @@ -1812,6 +1983,7 @@ where .collect() }) .named(new_subject_name) + .represented_by(representation) } } diff --git a/src/derived_spec/tests.rs b/src/derived_spec/tests.rs index 654ab85..4d855f1 100644 --- a/src/derived_spec/tests.rs +++ b/src/derived_spec/tests.rs @@ -226,7 +226,7 @@ fn verify_that_subject_satisfies_predicate_fails() { assert_eq!( failures, - &["expected answer.val to satisfy the given predicate, but returned false\n"] + &["expected answer.val to satisfy the given predicate, but returned false\n actual: 51\n"] ); } diff --git a/src/equality.rs b/src/equality.rs index eae0280..ae5dbe6 100644 --- a/src/equality.rs +++ b/src/equality.rs @@ -9,16 +9,17 @@ use crate::expectations::{ is_equal_to, is_same_as, not, }; use crate::spec::{ - DiffFormat, Expectation, Expecting, Expression, FailingStrategy, Invertible, Spec, + DiffFormat, Expectation, Expecting, Expression, FailingStrategy, Invertible, Represent, + Represented, Spec, }; use crate::std::fmt::{Debug, Display}; use crate::std::format; use crate::std::string::{String, ToString}; -impl AssertEquality for Spec<'_, S, R> +impl AssertEquality for Spec<'_, S, D, R> where - S: PartialEq + Debug, - E: Debug, + S: PartialEq, + D: Represent + Represent, R: FailingStrategy, { fn is_equal_to(self, expected: E) -> Self { @@ -30,10 +31,10 @@ where } } -impl Expectation for IsEqualTo +impl Expectation for IsEqualTo where - S: PartialEq + Debug, - E: Debug, + S: PartialEq, + D: Represent + Represent, { fn test(&mut self, subject: &S) -> bool { subject == &self.expected @@ -44,23 +45,26 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let not = if inverted { "not " } else { "" }; let expected = &self.expected; - let (marked_actual, marked_expected) = mark_diff(actual, expected, format); + let represented_expected = Represented::from((expected, representation)); + let (marked_actual, marked_expected) = mark_diff(actual, expected, representation, format); format!( - "expected {expression} to be {not}equal to {expected:?}\n but was: {marked_actual}\n expected: {not}{marked_expected}", + "expected {expression} to be {not}equal to {represented_expected:?}\n but was: {marked_actual}\n expected: {not}{marked_expected}", ) } } impl Invertible for IsEqualTo {} -impl AssertSameAs for Spec<'_, S, R> +impl AssertSameAs for Spec<'_, S, D, R> where - S: PartialEq + Debug, + S: PartialEq, R: FailingStrategy, + D: Represent, { fn is_same_as(self, expected: S) -> Self { self.expecting(is_same_as(expected)) @@ -71,9 +75,10 @@ where } } -impl Expectation for IsSameAs +impl Expectation for IsSameAs where - S: PartialEq + Debug, + S: PartialEq, + D: Represent, { fn test(&mut self, subject: &S) -> bool { subject == &self.expected @@ -84,20 +89,22 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let not = if inverted { "not " } else { "" }; let expected = &self.expected; - let (marked_actual, marked_expected) = mark_diff(actual, expected, format); + let represented_expected = Represented::from((expected, representation)); + let (marked_actual, marked_expected) = mark_diff(actual, expected, representation, format); format!( - "expected {expression} to be {not}the same as {expected:?}\n but was: {marked_actual}\n expected: {not}{marked_expected}", + "expected {expression} to be {not}the same as {represented_expected:?}\n but was: {marked_actual}\n expected: {not}{marked_expected}", ) } } impl Invertible for IsSameAs {} -impl AssertHasDebugString for Spec<'_, S, R> +impl AssertHasDebugString for Spec<'_, S, D, R> where S: Debug, E: AsRef, @@ -112,7 +119,7 @@ where } } -impl Expectation for HasDebugString +impl Expectation for HasDebugString where S: Debug, E: AsRef, @@ -126,6 +133,7 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + _representation: &D, format: &DiffFormat, ) -> String { let not = if inverted { "not " } else { "" }; @@ -140,7 +148,7 @@ where impl Invertible for HasDebugString {} -impl AssertHasDisplayString for Spec<'_, S, R> +impl AssertHasDisplayString for Spec<'_, S, D, R> where S: Display, E: AsRef, @@ -155,7 +163,7 @@ where } } -impl Expectation for HasDisplayString +impl Expectation for HasDisplayString where S: Display, E: AsRef, @@ -169,6 +177,7 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + _representation: &D, format: &DiffFormat, ) -> String { let not = if inverted { "not " } else { "" }; diff --git a/src/error/mod.rs b/src/error/mod.rs index b2aa34f..469502c 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -1,21 +1,23 @@ use crate::assertions::AssertErrorHasSource; -use crate::colored::{mark_missing, mark_missing_string, mark_unexpected, mark_unexpected_string}; +use crate::colored::{mark_missing, mark_unexpected}; use crate::expectations::{ ErrorHasSource, ErrorHasSourceMessage, error_has_source, error_has_source_message, not, }; use crate::spec::{ - DiffFormat, Expectation, Expecting, Expression, FailingStrategy, Invertible, Spec, + DebugRepresentation, DiffFormat, DisplayRepresentation, Expectation, Expecting, Expression, + FailingStrategy, Invertible, Represent, Spec, }; use crate::std::error::Error; use crate::std::format; use crate::std::string::{String, ToString}; -impl<'a, S, R> AssertErrorHasSource for Spec<'a, S, R> +impl<'a, S, D, R> AssertErrorHasSource for Spec<'a, S, D, R> where S: Error, + D: Represent, R: FailingStrategy, { - type SourceMessage = Spec<'a, Option, R>; + type SourceMessage = Spec<'a, Option, DebugRepresentation, R>; fn has_no_source(self) -> Self { self.expecting(not(error_has_source())) @@ -32,9 +34,10 @@ where } } -impl Expectation for ErrorHasSource +impl Expectation for ErrorHasSource where S: Error, + D: Represent, { fn test(&mut self, subject: &S) -> bool { subject.source().is_some() @@ -45,6 +48,7 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let (a, expected) = if inverted { @@ -52,8 +56,8 @@ where } else { ("a", "") }; - let marked_actual = mark_unexpected(actual, format); - let marked_expected = mark_missing_string(expected, format); + let marked_actual = mark_unexpected(actual, representation, format); + let marked_expected = mark_missing(expected, &DisplayRepresentation, format); format!( "expected {expression} to have {a} source\n but was: {marked_actual}\n expected: {marked_expected}" ) @@ -62,9 +66,10 @@ where impl Invertible for ErrorHasSource {} -impl Expectation for ErrorHasSourceMessage +impl Expectation for ErrorHasSourceMessage where S: Error, + D: Represent, { fn test(&mut self, subject: &S) -> bool { subject @@ -77,22 +82,24 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let not = if inverted { "not " } else { "" }; let expected = &self.expected_source_message; if let Some(actual_source) = actual.source() { - let marked_actual = mark_unexpected_string(&actual_source.to_string(), format); - let marked_expected = mark_missing_string(expected, format); + let marked_actual = + mark_unexpected(&actual_source.to_string(), &DisplayRepresentation, format); + let marked_expected = mark_missing(expected, &DisplayRepresentation, format); format!( "expected {expression} to have a source message {not}equal to \"{expected}\"\n but was: \"{marked_actual}\"\n expected: \"{marked_expected}\"" ) } else { - let mut marked_actual = mark_unexpected(actual, format); + let mut marked_actual = mark_unexpected(actual, representation, format); marked_actual.push_str(" - which has no source"); - let marked_expected = mark_missing(expected, format); + let marked_expected = mark_missing(expected, &DisplayRepresentation, format); format!( - "expected {expression} to have a source message {not}equal to \"{expected}\"\n but was: {marked_actual}\n expected: {not}{marked_expected}" + "expected {expression} to have a source message {not}equal to \"{expected}\"\n but was: {marked_actual}\n expected: {not}\"{marked_expected}\"" ) } } diff --git a/src/error/tests.rs b/src/error/tests.rs index 8701454..3f08477 100644 --- a/src/error/tests.rs +++ b/src/error/tests.rs @@ -353,7 +353,7 @@ mod colored { &[ "expected subject to have a source message equal to \"foo error\"\n \ but was: \u{1b}[31mFoo\u{1b}[0m - which has no source\n \ - expected: \u{1b}[33m\"foo error\"\u{1b}[0m\n\ + expected: \"\u{1b}[33mfoo error\u{1b}[0m\"\n\ " ] ); diff --git a/src/expectation_combinators/mod.rs b/src/expectation_combinators/mod.rs index cfdbf8a..cb52f2d 100644 --- a/src/expectation_combinators/mod.rs +++ b/src/expectation_combinators/mod.rs @@ -2,9 +2,9 @@ use crate::expectations::{All, Any, IntoRec, Not, Rec}; use crate::spec::{DiffFormat, Expectation, Expression, Invertible}; use crate::std::string::String; -impl Expectation for Rec +impl Expectation for Rec where - E: Expectation, + E: Expectation, { fn test(&mut self, subject: &S) -> bool { let result = self.expectation.test(subject); @@ -17,11 +17,12 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { if self.is_failure() { self.expectation - .message(expression, actual, inverted, format) + .message(expression, actual, inverted, representation, format) + "\n" } else { String::new() @@ -62,9 +63,9 @@ impl_into_rec_for_tuple! { A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 } impl_into_rec_for_tuple! { A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 } impl_into_rec_for_tuple! { A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 } -impl Expectation for Not +impl Expectation for Not where - E: Invertible + Expectation, + E: Invertible + Expectation, { fn test(&mut self, subject: &S) -> bool { !self.0.test(subject) @@ -75,16 +76,18 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { - self.0.message(expression, actual, !inverted, format) + self.0 + .message(expression, actual, !inverted, representation, format) } } macro_rules! impl_expectation_for_all_combinator { ( $( $tp_name:ident )+ ) => { #[allow(non_snake_case)] - impl),+> Expectation for All<($(Rec<$tp_name>,)+)> { + impl),+> Expectation for All<($(Rec<$tp_name>,)+)> { fn test(&mut self, subject: &S) -> bool { let ($($tp_name,)+) = &mut self.0; $( @@ -98,12 +101,13 @@ macro_rules! impl_expectation_for_all_combinator { expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let ($($tp_name,)+) = &self.0; let mut message = String::new(); $( - message.push_str(&$tp_name.message(expression, actual, inverted, format)); + message.push_str(&$tp_name.message(expression, actual, inverted, representation, format)); )+ message } @@ -127,7 +131,7 @@ impl_expectation_for_all_combinator! { A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 } macro_rules! impl_expectation_for_any_combinator { ( $( $tp_name:ident )+ ) => { #[allow(non_snake_case)] - impl),+> Expectation for Any<($(Rec<$tp_name>,)+)> { + impl),+> Expectation for Any<($(Rec<$tp_name>,)+)> { fn test(&mut self, subject: &S) -> bool { let ($($tp_name,)+) = &mut self.0; $( @@ -141,12 +145,13 @@ macro_rules! impl_expectation_for_any_combinator { expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let ($($tp_name,)+) = &self.0; let mut message = String::new(); $( - message.push_str(&$tp_name.message(expression, actual, inverted, format)); + message.push_str(&$tp_name.message(expression, actual, inverted, representation, format)); )+ message } diff --git a/src/expectation_combinators/tests.rs b/src/expectation_combinators/tests.rs index 1870ead..2f773fa 100644 --- a/src/expectation_combinators/tests.rs +++ b/src/expectation_combinators/tests.rs @@ -1,9 +1,9 @@ use crate::expectations::{ - IsBetween, IsEmpty, IsGreaterThan, IsLessThan, IsNegative, IsOne, IsPositive, IsZero, + IsBetween, IsEmpty, IsGreaterThan, IsLessThan, IsNegative, IsOne, IsPositive, IsZero, Rec, StringContains, StringContainsAnyOf, all, any, not, rec, }; use crate::prelude::*; -use crate::spec::{Expectation, Expression}; +use crate::spec::{DebugRepresentation, Expectation, Expression}; #[test] fn newly_created_rec_combinator_is_neither_success_nor_failure() { @@ -17,7 +17,7 @@ fn newly_created_rec_combinator_is_neither_success_nor_failure() { fn rec_combinator_is_success_after_test_method_has_been_called() { let mut rec = rec(IsZero); - rec.test(&0); + as Expectation>::test(&mut rec, &0); assert_that(rec.is_success()).is_true(); assert_that(rec.is_failure()).is_false(); @@ -27,7 +27,7 @@ fn rec_combinator_is_success_after_test_method_has_been_called() { fn rec_combinator_is_failure_after_test_method_has_been_called() { let mut rec = rec(IsNegative); - rec.test(&1); + as Expectation>::test(&mut rec, &1); assert_that(rec.is_failure()).is_true(); assert_that(rec.is_success()).is_false(); @@ -37,11 +37,12 @@ fn rec_combinator_is_failure_after_test_method_has_been_called() { fn rec_combinator_returns_empty_message_if_test_is_successful() { let mut rec = rec(IsGreaterThan { expected: 10 }); - rec.test(&12); + > as Expectation>::test(&mut rec, &12); let message = rec.message( &Expression::from("foo"), &12, false, + &DebugRepresentation, &DIFF_FORMAT_NO_HIGHLIGHT, ); @@ -52,11 +53,12 @@ fn rec_combinator_returns_empty_message_if_test_is_successful() { fn rec_combinator_returns_failure_message_if_test_is_failure() { let mut rec = rec(IsOne); - rec.test(&12); + as Expectation>::test(&mut rec, &12); let message = rec.message( &Expression::from("foo"), &12, false, + &DebugRepresentation, &DIFF_FORMAT_NO_HIGHLIGHT, ); diff --git a/src/expectations.rs b/src/expectations.rs index 48ccd2c..424c3a9 100644 --- a/src/expectations.rs +++ b/src/expectations.rs @@ -114,7 +114,7 @@ pub fn rec(expectations: E) -> Rec { /// ``` /// use asserting::prelude::*; /// use asserting::expectations::{IsNegative, rec}; -/// use asserting::spec::Expectation; +/// use asserting::spec::{DebugRepresentation, Expectation}; /// /// // the result of new `Rec` is neither `success` nor `failure` /// let mut expectation = rec(IsNegative); @@ -123,13 +123,13 @@ pub fn rec(expectations: E) -> Rec { /// /// // once the `test` method has been called, the result can be queried at a /// // later time. -/// _ = expectation.test(&-42); // returns true +/// _ = Expectation::<_, DebugRepresentation>::test(&mut expectation, &-42); // returns true /// assert_that!(expectation.is_success()).is_true(); /// assert_that!(expectation.is_failure()).is_false(); /// /// // once the `test` method has been called, the result can be queried at a /// // later time. -/// _= expectation.test(&42); // returns false +/// _ = Expectation::<_, DebugRepresentation>::test(&mut expectation, &42); // returns false /// assert_that!(expectation.is_success()).is_false(); /// assert_that!(expectation.is_failure()).is_true(); /// ``` @@ -841,13 +841,13 @@ pub struct StringContainsAnyOf { pub expected: E, } -/// Creates a [`StringStartWith`] expectation. -pub fn string_starts_with(expected: E) -> StringStartWith { - StringStartWith { expected } +/// Creates a [`StringStartsWith`] expectation. +pub fn string_starts_with(expected: E) -> StringStartsWith { + StringStartsWith { expected } } #[must_use] -pub struct StringStartWith { +pub struct StringStartsWith { pub expected: E, } diff --git a/src/float/mod.rs b/src/float/mod.rs index 245de38..df1fd09 100644 --- a/src/float/mod.rs +++ b/src/float/mod.rs @@ -99,13 +99,15 @@ mod cmp { use crate::colored::mark_diff; use crate::expectations::{IsCloseTo, is_close_to, not}; use crate::spec::{ - DiffFormat, Expectation, Expecting, Expression, FailingStrategy, Invertible, Spec, + DiffFormat, Expectation, Expecting, Expression, FailingStrategy, Invertible, Represent, + Spec, }; use crate::std::{format, string::String}; use float_cmp::{ApproxEq, F32Margin, F64Margin}; - impl AssertIsCloseToWithDefaultMargin for Spec<'_, f32, R> + impl AssertIsCloseToWithDefaultMargin for Spec<'_, f32, D, R> where + D: Represent, R: FailingStrategy, { fn is_close_to(self, expected: f32) -> Self { @@ -119,8 +121,9 @@ mod cmp { } } - impl AssertIsCloseToWithinMargin for Spec<'_, f32, R> + impl AssertIsCloseToWithinMargin for Spec<'_, f32, D, R> where + D: Represent, R: FailingStrategy, { fn is_close_to_with_margin(self, expected: f32, margin: impl Into) -> Self { @@ -132,8 +135,9 @@ mod cmp { } } - impl AssertIsCloseToWithDefaultMargin for Spec<'_, f64, R> + impl AssertIsCloseToWithDefaultMargin for Spec<'_, f64, D, R> where + D: Represent, R: FailingStrategy, { fn is_close_to(self, expected: f64) -> Self { @@ -147,8 +151,9 @@ mod cmp { } } - impl AssertIsCloseToWithinMargin for Spec<'_, f64, R> + impl AssertIsCloseToWithinMargin for Spec<'_, f64, D, R> where + D: Represent, R: FailingStrategy, { fn is_close_to_with_margin(self, expected: f64, margin: impl Into) -> Self { @@ -160,7 +165,10 @@ mod cmp { } } - impl Expectation for IsCloseTo { + impl Expectation for IsCloseTo + where + D: Represent, + { fn test(&mut self, subject: &f32) -> bool { subject.approx_eq(self.expected, self.margin) } @@ -170,10 +178,12 @@ mod cmp { expression: &Expression<'_>, actual: &f32, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let not = if inverted { "not " } else { "" }; - let (marked_actual, marked_expected) = mark_diff(actual, &self.expected, format); + let (marked_actual, marked_expected) = + mark_diff(actual, &self.expected, representation, format); format!( "expected {expression} to be {not}close to {:?}\n within a margin of epsilon={:e} and ulps={}\n but was: {marked_actual}\n expected: {marked_expected}", self.expected, self.margin.epsilon, self.margin.ulps @@ -183,7 +193,10 @@ mod cmp { impl Invertible for IsCloseTo {} - impl Expectation for IsCloseTo { + impl Expectation for IsCloseTo + where + D: Represent, + { fn test(&mut self, subject: &f64) -> bool { subject.approx_eq(self.expected, self.margin) } @@ -193,10 +206,12 @@ mod cmp { expression: &Expression<'_>, actual: &f64, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let not = if inverted { "not " } else { "" }; - let (marked_actual, marked_expected) = mark_diff(actual, &self.expected, format); + let (marked_actual, marked_expected) = + mark_diff(actual, &self.expected, representation, format); format!( "expected {expression} to be {not}close to {:?}\n within a margin of epsilon={:e} and ulps={}\n but was: {marked_actual}\n expected: {marked_expected}", self.expected, self.margin.epsilon, self.margin.ulps diff --git a/src/iterator/mod.rs b/src/iterator/mod.rs index 0872602..73104f4 100644 --- a/src/iterator/mod.rs +++ b/src/iterator/mod.rs @@ -5,8 +5,7 @@ use crate::assertions::{ AssertIteratorContainsInOrder, AssertOrderedElements, AssertOrderedElementsRef, }; use crate::colored::{ - mark_all_items_in_collection, mark_missing, mark_missing_string, - mark_selected_items_in_collection, mark_unexpected, mark_unexpected_string, + mark_all_items_in_collection, mark_missing, mark_selected_items_in_collection, mark_unexpected, }; use crate::derived_spec::DerivedSpec; use crate::expectations::{ @@ -22,40 +21,43 @@ use crate::expectations::{ }; use crate::properties::DefinedOrderProperty; use crate::spec::{ - DiffFormat, Expectation, Expecting, Expression, FailingStrategy, GetFailures, Invertible, - PanicOnFail, Spec, + DiffFormat, DisplayRepresentation, Expectation, Expecting, Expression, FailingStrategy, + GetFailures, Invertible, PanicOnFail, Represent, Represented, RepresentedBy, Spec, }; use crate::std::borrow::ToOwned; use crate::std::cmp::Ordering; -use crate::std::fmt::Debug; use crate::std::mem; use crate::std::{format, string::String, vec, vec::Vec}; use hashbrown::HashSet; -impl<'a, S, T, E, R> AssertIteratorContains for Spec<'a, S, R> +impl<'a, S, T, E, D, R> AssertIteratorContains for Spec<'a, S, D, R> where S: IntoIterator, - T: PartialEq + Debug, - E: Debug, + T: PartialEq, + D: Represent + Represent + Clone, R: FailingStrategy, { - type Sequence = Spec<'a, Vec, R>; + type Sequence = Spec<'a, Vec, D, R>; fn contains(self, expected: E) -> Self::Sequence { + let representation = self.representation().clone(); self.mapping(Vec::from_iter) + .represented_by(representation) .expecting(iterator_contains(expected)) } fn does_not_contain(self, expected: E) -> Self::Sequence { + let representation = self.representation().clone(); self.mapping(Vec::from_iter) + .represented_by(representation) .expecting(not(iterator_contains(expected))) } } -impl Expectation> for IteratorContains +impl Expectation, D> for IteratorContains where - T: PartialEq + Debug, - E: Debug, + T: PartialEq, + D: Represent + Represent, { fn test(&mut self, subject: &Vec) -> bool { subject.iter().any(|e| e == &self.expected) @@ -66,6 +68,7 @@ where expression: &Expression<'_>, actual: &Vec, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let (not, marked_actual) = if inverted { @@ -83,69 +86,83 @@ where let marked_actual = mark_selected_items_in_collection( actual, &found_unexpected, + representation, format, mark_unexpected, ); ("not ", marked_actual) } else { - let marked_actual = mark_all_items_in_collection(actual, format, mark_unexpected); + let marked_actual = + mark_all_items_in_collection(actual, representation, format, mark_unexpected); ("", marked_actual) }; - let marked_expected = mark_missing(&self.expected, format); + let marked_expected = mark_missing(&self.expected, representation, format); + let represented_expected = Represented::from((&self.expected, representation)); format!( - "expected {expression} to {not}contain {:?}\n but was: {marked_actual}\n expected: {not}{marked_expected}", - self.expected, + "expected {expression} to {not}contain {represented_expected:?}\n but was: {marked_actual}\n expected: {not}{marked_expected}", ) } } impl Invertible for IteratorContains {} -impl<'a, S, T, E, R> AssertIteratorContainsInAnyOrder for Spec<'a, S, R> +impl<'a, S, T, E, D, R> AssertIteratorContainsInAnyOrder for Spec<'a, S, D, R> where S: IntoIterator, - T: PartialEq<::Item> + Debug, + T: PartialEq<::Item>, E: IntoIterator, - ::Item: Debug, + D: Represent + Represent<::Item> + Clone, R: FailingStrategy, { - type Sequence = Spec<'a, Vec, R>; + type Sequence = Spec<'a, Vec, D, R>; fn contains_exactly_in_any_order(self, expected: E) -> Self::Sequence { + let representation = self.representation().clone(); self.mapping(Vec::from_iter) + .represented_by(representation) .expecting(iterator_contains_exactly_in_any_order(expected)) } - fn contains_any_of(self, expected: E) -> Spec<'a, Vec, R> { + fn contains_any_of(self, expected: E) -> Self::Sequence { + let representation = self.representation().clone(); self.mapping(Vec::from_iter) + .represented_by(representation) .expecting(iterator_contains_any_of(expected)) } - fn does_not_contain_any_of(self, expected: E) -> Spec<'a, Vec, R> { + fn does_not_contain_any_of(self, expected: E) -> Self::Sequence { + let representation = self.representation().clone(); self.mapping(Vec::from_iter) + .represented_by(representation) .expecting(not(iterator_contains_any_of(expected))) } - fn contains_all_of(self, expected: E) -> Spec<'a, Vec, R> { + fn contains_all_of(self, expected: E) -> Self::Sequence { + let representation = self.representation().clone(); self.mapping(Vec::from_iter) + .represented_by(representation) .expecting(iterator_contains_all_of(expected)) } - fn contains_only(self, expected: E) -> Spec<'a, Vec, R> { + fn contains_only(self, expected: E) -> Self::Sequence { + let representation = self.representation().clone(); self.mapping(Vec::from_iter) + .represented_by(representation) .expecting(iterator_contains_only(expected)) } - fn contains_only_once(self, expected: E) -> Spec<'a, Vec, R> { + fn contains_only_once(self, expected: E) -> Self::Sequence { + let representation = self.representation().clone(); self.mapping(Vec::from_iter) + .represented_by(representation) .expecting(iterator_contains_only_once(expected)) } } -impl Expectation> for IteratorContainsExactlyInAnyOrder +impl Expectation, D> for IteratorContainsExactlyInAnyOrder where - T: PartialEq + Debug, - E: Debug, + T: PartialEq, + D: Represent + Represent, { fn test(&mut self, subject: &Vec) -> bool { let missing = &mut self.missing; @@ -173,30 +190,45 @@ where expression: &Expression<'_>, actual: &Vec, _inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { - let missing = collect_selected_values(&self.missing, &self.expected); - let extra = collect_selected_values(&self.extra, actual); - let marked_actual = - mark_selected_items_in_collection(actual, &self.extra, format, mark_unexpected); - let marked_expected = - mark_selected_items_in_collection(&self.expected, &self.missing, format, mark_missing); + let missing = collect_selected_values(&self.missing, &self.expected, representation); + let extra = collect_selected_values(&self.extra, actual, representation); + let marked_actual = mark_selected_items_in_collection( + actual, + &self.extra, + representation, + format, + mark_unexpected, + ); + let marked_expected = mark_selected_items_in_collection( + &self.expected, + &self.missing, + representation, + format, + mark_missing, + ); + let represented_expected = self + .expected + .iter() + .map(|e| Represented::from((e, representation))) + .collect::>(); format!( - r"expected {expression} to contain exactly in any order {:?} + r"expected {expression} to contain exactly in any order {represented_expected:?} but was: {marked_actual} expected: {marked_expected} missing: {missing:?} extra: {extra:?}", - self.expected ) } } -impl Expectation> for IteratorContainsAnyOf +impl Expectation, D> for IteratorContainsAnyOf where - T: PartialEq + Debug, - E: Debug, + T: PartialEq, + D: Represent + Represent, { fn test(&mut self, subject: &Vec) -> bool { for expected in &self.expected { @@ -212,6 +244,7 @@ where expression: &Expression<'_>, actual: &Vec, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let (not, marked_actual, marked_expected) = if inverted { @@ -237,37 +270,44 @@ where let marked_actual = mark_selected_items_in_collection( actual, &found_in_actual, + representation, format, mark_unexpected, ); let marked_expected = mark_selected_items_in_collection( &self.expected, &found_in_expected, + representation, format, mark_missing, ); ("not ", marked_actual, marked_expected) } else { - let marked_actual = mark_all_items_in_collection(actual, format, mark_unexpected); + let marked_actual = + mark_all_items_in_collection(actual, representation, format, mark_unexpected); let marked_expected = - mark_all_items_in_collection(&self.expected, format, mark_missing); + mark_all_items_in_collection(&self.expected, representation, format, mark_missing); ("", marked_actual, marked_expected) }; + let represented_expected = self + .expected + .iter() + .map(|e| Represented::from((e, representation))) + .collect::>(); format!( - r"expected {expression} to {not}contain any of {:?} + r"expected {expression} to {not}contain any of {represented_expected:?} but was: {marked_actual} expected: {not}{marked_expected}", - self.expected, ) } } impl Invertible for IteratorContainsAnyOf {} -impl Expectation> for IteratorContainsAllOf +impl Expectation, D> for IteratorContainsAllOf where - T: PartialEq + Debug, - E: Debug, + T: PartialEq, + D: Represent + Represent, { fn test(&mut self, subject: &Vec) -> bool { let missing = &mut self.missing; @@ -286,6 +326,7 @@ where expression: &Expression<'_>, actual: &Vec, _inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let mut extra = HashSet::new(); @@ -294,26 +335,40 @@ where extra.insert(actual_index); } } - let marked_actual = - mark_selected_items_in_collection(actual, &extra, format, mark_unexpected); - let marked_expected = - mark_selected_items_in_collection(&self.expected, &self.missing, format, mark_missing); - let missing = collect_selected_values(&self.missing, &self.expected); + let marked_actual = mark_selected_items_in_collection( + actual, + &extra, + representation, + format, + mark_unexpected, + ); + let marked_expected = mark_selected_items_in_collection( + &self.expected, + &self.missing, + representation, + format, + mark_missing, + ); + let missing = collect_selected_values(&self.missing, &self.expected, representation); + let represented_expected = self + .expected + .iter() + .map(|e| Represented::from((e, representation))) + .collect::>(); format!( - r"expected {expression} to contain all of {:?} + r"expected {expression} to contain all of {represented_expected:?} but was: {marked_actual} expected: {marked_expected} missing: {missing:?}", - self.expected, ) } } -impl Expectation> for IteratorContainsOnly +impl Expectation, D> for IteratorContainsOnly where - T: PartialEq + Debug, - E: Debug, + T: PartialEq, + D: Represent + Represent, { fn test(&mut self, subject: &Vec) -> bool { let extra = &mut self.extra; @@ -332,6 +387,7 @@ where expression: &Expression<'_>, actual: &Vec, _inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let mut missing = HashSet::new(); @@ -340,26 +396,40 @@ where missing.insert(expected_index); } } - let marked_actual = - mark_selected_items_in_collection(actual, &self.extra, format, mark_unexpected); - let marked_expected = - mark_selected_items_in_collection(&self.expected, &missing, format, mark_missing); - let extra = collect_selected_values(&self.extra, actual); + let marked_actual = mark_selected_items_in_collection( + actual, + &self.extra, + representation, + format, + mark_unexpected, + ); + let marked_expected = mark_selected_items_in_collection( + &self.expected, + &missing, + representation, + format, + mark_missing, + ); + let extra = collect_selected_values(&self.extra, actual, representation); + let represented_expected = self + .expected + .iter() + .map(|e| Represented::from((e, representation))) + .collect::>(); format!( - r"expected {expression} to contain only {:?} + r"expected {expression} to contain only {represented_expected:?} but was: {marked_actual} expected: {marked_expected} extra: {extra:?}", - self.expected, ) } } -impl Expectation> for IteratorContainsOnlyOnce +impl Expectation, D> for IteratorContainsOnlyOnce where - T: PartialEq + Debug, - E: Debug, + T: PartialEq, + D: Represent + Represent, { fn test(&mut self, subject: &Vec) -> bool { let extra = &mut self.extra; @@ -383,19 +453,23 @@ where expression: &Expression<'_>, actual: &Vec, _inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let actual_duplicates_and_extras = self.duplicates.union(&self.extra).copied().collect(); let marked_actual = mark_selected_items_in_collection( actual, &actual_duplicates_and_extras, + representation, format, mark_unexpected, ); - let duplicates = collect_selected_values(&self.duplicates, actual); + let duplicates = collect_selected_values(&self.duplicates, actual, representation); let mut expected_duplicates_and_missing = HashSet::new(); for (expected_index, expected) in self.expected.iter().enumerate() { - if duplicates.iter().any(|duplicate| *duplicate == expected) + if duplicates + .iter() + .any(|duplicate| duplicate.value == expected) || !actual.iter().any(|actual| actual == expected) { expected_duplicates_and_missing.insert(expected_index); @@ -404,64 +478,79 @@ where let marked_expected = mark_selected_items_in_collection( &self.expected, &expected_duplicates_and_missing, + representation, format, mark_missing, ); - let extra = collect_selected_values(&self.extra, actual); + let extra = collect_selected_values(&self.extra, actual, representation); + let represented_expected = self + .expected + .iter() + .map(|e| Represented::from((e, representation))) + .collect::>(); format!( - r"expected {expression} to contain only once {:?} + r"expected {expression} to contain only once {represented_expected:?} but was: {marked_actual} expected: {marked_expected} extra: {extra:?} duplicates: {duplicates:?}", - self.expected, ) } } -impl<'a, S, T, E, R> AssertIteratorContainsInOrder for Spec<'a, S, R> +impl<'a, S, T, E, D, R> AssertIteratorContainsInOrder for Spec<'a, S, D, R> where S: IntoIterator, ::IntoIter: DefinedOrderProperty, E: IntoIterator, ::IntoIter: DefinedOrderProperty, - ::Item: Debug, - T: PartialEq<::Item> + Debug, + T: PartialEq<::Item>, + D: Represent + Represent<::Item> + Clone, R: FailingStrategy, { - type Sequence = Spec<'a, Vec, R>; + type Sequence = Spec<'a, Vec, D, R>; fn contains_exactly(self, expected: E) -> Self::Sequence { + let representation = self.representation().clone(); self.mapping(Vec::from_iter) + .represented_by(representation) .expecting(iterator_contains_exactly(expected)) } fn contains_sequence(self, expected: E) -> Self::Sequence { + let representation = self.representation().clone(); self.mapping(Vec::from_iter) + .represented_by(representation) .expecting(iterator_contains_sequence(expected)) } fn contains_all_in_order(self, expected: E) -> Self::Sequence { + let representation = self.representation().clone(); self.mapping(Vec::from_iter) + .represented_by(representation) .expecting(iterator_contains_all_in_order(expected)) } fn starts_with(self, expected: E) -> Self::Sequence { + let representation = self.representation().clone(); self.mapping(Vec::from_iter) + .represented_by(representation) .expecting(iterator_starts_with(expected)) } fn ends_with(self, expected: E) -> Self::Sequence { + let representation = self.representation().clone(); self.mapping(Vec::from_iter) + .represented_by(representation) .expecting(iterator_ends_with(expected)) } } -impl Expectation> for IteratorContainsExactly +impl Expectation, D> for IteratorContainsExactly where - T: PartialEq + Debug, - E: Debug, + T: PartialEq, + D: Represent + Represent, { fn test(&mut self, subject: &Vec) -> bool { let mut maybe_extras = Vec::new(); @@ -510,44 +599,55 @@ where expression: &Expression<'_>, actual: &Vec, _inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { - let out_of_order = collect_selected_values(&self.out_of_order, actual); + let out_of_order = collect_selected_values(&self.out_of_order, actual, representation); let mut expected_indices = self.missing.clone(); for (expected_index, expected) in self.expected.iter().enumerate() { - if out_of_order.iter().any(|actual| *actual == expected) { + if out_of_order.iter().any(|actual| actual.value == expected) { expected_indices.insert(expected_index); } } let marked_expected = mark_selected_items_in_collection( &self.expected, &expected_indices, + representation, format, mark_missing, ); let actual_indices = self.extra.union(&self.out_of_order).copied().collect(); - let marked_actual = - mark_selected_items_in_collection(actual, &actual_indices, format, mark_unexpected); + let marked_actual = mark_selected_items_in_collection( + actual, + &actual_indices, + representation, + format, + mark_unexpected, + ); - let missing = collect_selected_values(&self.missing, &self.expected); - let extra = collect_selected_values(&self.extra, actual); + let missing = collect_selected_values(&self.missing, &self.expected, representation); + let extra = collect_selected_values(&self.extra, actual, representation); + let represented_expected = self + .expected + .iter() + .map(|e| Represented::from((e, representation))) + .collect::>(); format!( - r"expected {expression} to contain exactly in order {:?} + r"expected {expression} to contain exactly in order {represented_expected:?} but was: {marked_actual} expected: {marked_expected} missing: {missing:?} extra: {extra:?} out-of-order: {out_of_order:?}", - self.expected, ) } } -impl Expectation> for IteratorContainsSequence +impl Expectation, D> for IteratorContainsSequence where - T: PartialEq + Debug, - E: Debug, + T: PartialEq, + D: Represent + Represent, { fn test(&mut self, subject: &Vec) -> bool { let subject_length = subject.len(); @@ -615,30 +715,45 @@ where expression: &Expression<'_>, actual: &Vec, _inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { - let marked_actual = - mark_selected_items_in_collection(actual, &self.extra, format, mark_unexpected); - let marked_expected = - mark_selected_items_in_collection(&self.expected, &self.missing, format, mark_missing); - let missing = collect_selected_values(&self.missing, &self.expected); - let extra = collect_selected_values(&self.extra, actual); + let marked_actual = mark_selected_items_in_collection( + actual, + &self.extra, + representation, + format, + mark_unexpected, + ); + let marked_expected = mark_selected_items_in_collection( + &self.expected, + &self.missing, + representation, + format, + mark_missing, + ); + let missing = collect_selected_values(&self.missing, &self.expected, representation); + let extra = collect_selected_values(&self.extra, actual, representation); + let represented_expected = self + .expected + .iter() + .map(|e| Represented::from((e, representation))) + .collect::>(); format!( - r"expected {expression} to contain the sequence {:?} + r"expected {expression} to contain the sequence {represented_expected:?} but was: {marked_actual} expected: {marked_expected} missing: {missing:?} extra: {extra:?}", - self.expected, ) } } -impl Expectation> for IteratorContainsAllInOrder +impl Expectation, D> for IteratorContainsAllInOrder where - T: PartialEq + Debug, - E: Debug, + T: PartialEq, + D: Represent + Represent, { fn test(&mut self, subject: &Vec) -> bool { let missing = &mut self.missing; @@ -663,26 +778,40 @@ where expression: &Expression<'_>, actual: &Vec, _inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { - let marked_expected = - mark_selected_items_in_collection(&self.expected, &self.missing, format, mark_missing); - let missing = collect_selected_values(&self.missing, &self.expected); + let marked_expected = mark_selected_items_in_collection( + &self.expected, + &self.missing, + representation, + format, + mark_missing, + ); + let missing = collect_selected_values(&self.missing, &self.expected, representation); + let represented_expected = self + .expected + .iter() + .map(|e| Represented::from((e, representation))) + .collect::>(); + let represented_actual = actual + .iter() + .map(|s| Represented::from((s, representation))) + .collect::>(); format!( - r"expected {expression} to contain all of {:?} in order - but was: {actual:?} + r"expected {expression} to contain all of {represented_expected:?} in order + but was: {represented_actual:?} expected: {marked_expected} missing: {missing:?}", - self.expected, ) } } -impl Expectation> for IteratorStartsWith +impl Expectation, D> for IteratorStartsWith where - T: PartialEq + Debug, - E: Debug, + T: PartialEq, + D: Represent + Represent, { fn test(&mut self, subject: &Vec) -> bool { let missing = &mut self.missing; @@ -712,30 +841,45 @@ where expression: &Expression<'_>, actual: &Vec, _inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { - let marked_actual = - mark_selected_items_in_collection(actual, &self.extra, format, mark_unexpected); - let marked_expected = - mark_selected_items_in_collection(&self.expected, &self.missing, format, mark_missing); - let missing = collect_selected_values(&self.missing, &self.expected); - let extra = collect_selected_values(&self.extra, actual); + let marked_actual = mark_selected_items_in_collection( + actual, + &self.extra, + representation, + format, + mark_unexpected, + ); + let marked_expected = mark_selected_items_in_collection( + &self.expected, + &self.missing, + representation, + format, + mark_missing, + ); + let missing = collect_selected_values(&self.missing, &self.expected, representation); + let extra = collect_selected_values(&self.extra, actual, representation); + let represented_expected = self + .expected + .iter() + .map(|e| Represented::from((e, representation))) + .collect::>(); format!( - r"expected {expression} to start with {:?} + r"expected {expression} to start with {represented_expected:?} but was: {marked_actual} expected: {marked_expected} missing: {missing:?} extra: {extra:?}", - self.expected, ) } } -impl Expectation> for IteratorEndsWith +impl Expectation, D> for IteratorEndsWith where - T: PartialEq + Debug, - E: Debug, + T: PartialEq, + D: Represent + Represent, { fn test(&mut self, subject: &Vec) -> bool { let missing = &mut self.missing; @@ -765,63 +909,87 @@ where expression: &Expression<'_>, actual: &Vec, _inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { - let marked_actual = - mark_selected_items_in_collection(actual, &self.extra, format, mark_unexpected); - let marked_expected = - mark_selected_items_in_collection(&self.expected, &self.missing, format, mark_missing); - let missing = collect_selected_values(&self.missing, &self.expected); - let extra = collect_selected_values(&self.extra, actual); + let marked_actual = mark_selected_items_in_collection( + actual, + &self.extra, + representation, + format, + mark_unexpected, + ); + let marked_expected = mark_selected_items_in_collection( + &self.expected, + &self.missing, + representation, + format, + mark_missing, + ); + let missing = collect_selected_values(&self.missing, &self.expected, representation); + let extra = collect_selected_values(&self.extra, actual, representation); + let represented_expected = self + .expected + .iter() + .map(|e| Represented::from((e, representation))) + .collect::>(); format!( - r"expected {expression} to end with {:?} + r"expected {expression} to end with {represented_expected:?} but was: {marked_actual} expected: {marked_expected} missing: {missing:?} extra: {extra:?}", - self.expected, ) } } -impl<'a, S, T, R> AssertFilteredElements for Spec<'a, S, R> +impl<'a, S, T, D, R> AssertFilteredElements for Spec<'a, S, D, R> where S: IntoIterator, - T: Debug, + D: Represent + Clone, R: FailingStrategy, { - type SingleElement = Spec<'a, T, R>; - type MultipleElements = Spec<'a, Vec, R>; + type SingleElement = Spec<'a, T, D, R>; + type MultipleElements = Spec<'a, Vec, D, R>; fn single_element(self) -> Self::SingleElement { - let spec = self.mapping(Vec::from_iter).expecting(has_single_element()); + let representation = self.representation().clone(); + let spec = self + .mapping(Vec::from_iter) + .represented_by(representation.clone()) + .expecting(has_single_element()); if spec.has_failures() { PanicOnFail.do_fail_with(&spec.failures()); unreachable!("Assertion failed and should have panicked! Please report a bug.") } let original_expression = spec.expression(); let new_expression = format!("{original_expression}'s only element"); - spec.extracting("", |mut collection| { + spec.extracting("[0]", |mut collection| { collection.pop().unwrap_or_else(|| { unreachable!("Assertion failed and should have panicked! Please report a bug.") }) }) .named(new_expression) + .represented_by(representation) } fn filtered_on(self, condition: C) -> Self::MultipleElements where C: FnMut(&T) -> bool, { + let representation = self.representation().clone(); self.mapping(|subject| subject.into_iter().filter(condition).collect()) + .represented_by(representation) } fn any_satisfies

(self, predicate: P) -> Self::MultipleElements where P: FnMut(&T) -> bool, { + let representation = self.representation().clone(); self.mapping(Vec::from_iter) + .represented_by(representation) .expecting(any_satisfies(predicate)) } @@ -829,7 +997,9 @@ where where P: FnMut(&T) -> bool, { + let representation = self.representation().clone(); self.mapping(Vec::from_iter) + .represented_by(representation) .expecting(all_satisfy(predicate)) } @@ -837,14 +1007,16 @@ where where P: FnMut(&T) -> bool, { + let representation = self.representation().clone(); self.mapping(Vec::from_iter) + .represented_by(representation) .expecting(none_satisfies(predicate)) } } -impl Expectation> for HasSingleElement +impl Expectation, D> for HasSingleElement where - T: Debug, + D: Represent, { fn test(&mut self, subject: &Vec) -> bool { subject.len() == 1 @@ -855,26 +1027,35 @@ where expression: &Expression<'_>, actual: &Vec, _inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let actual_length = actual.len(); let actual_elements = match actual_length { - 0 => mark_unexpected_string("no elements", format), - 1 => mark_unexpected_string("exactly one element", format), - _ => mark_unexpected_string(&format!("{actual_length} elements"), format), + 0 => mark_unexpected("no elements", &DisplayRepresentation, format), + 1 => mark_unexpected("exactly one element", &DisplayRepresentation, format), + _ => mark_unexpected( + &format!("{actual_length} elements"), + &DisplayRepresentation, + format, + ), }; - let expected_elements = mark_missing_string("exactly one element", format); + let expected_elements = mark_missing("exactly one element", &DisplayRepresentation, format); + let represented_actual = actual + .iter() + .map(|s| Represented::from((s, representation))) + .collect::>(); format!( r"expected {expression} to have {expected_elements}, but has {actual_elements} - actual: {actual:?}" + actual: {represented_actual:?}" ) } } -impl Expectation> for AnySatisfies

+impl Expectation, D> for AnySatisfies

where - T: Debug, P: FnMut(&T) -> bool, + D: Represent, { fn test(&mut self, subject: &Vec) -> bool { subject.iter().any(|e| (self.predicate)(e)) @@ -885,19 +1066,24 @@ where expression: &Expression<'_>, actual: &Vec, _inverted: bool, + representation: &D, _format: &DiffFormat, ) -> String { + let represented_actual = actual + .iter() + .map(|s| Represented::from((s, representation))) + .collect::>(); format!( r"expected any element of {expression} to satisfy the predicate, but none did - actual: {actual:?}" + actual: {represented_actual:?}" ) } } -impl Expectation> for AllSatisfy

+impl Expectation, D> for AllSatisfy

where - T: Debug, P: FnMut(&T) -> bool, + D: Represent, { fn test(&mut self, subject: &Vec) -> bool { for (i, e) in subject.iter().enumerate() { @@ -913,12 +1099,18 @@ where expression: &Expression<'_>, actual: &Vec, _inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let number_of_failing = self.failing.len(); - let failing = collect_selected_values(&self.failing, actual); - let marked_actual = - mark_selected_items_in_collection(actual, &self.failing, format, mark_unexpected); + let failing = collect_selected_values(&self.failing, actual, representation); + let marked_actual = mark_selected_items_in_collection( + actual, + &self.failing, + representation, + format, + mark_unexpected, + ); format!( r"expected all elements of {expression} to satisfy the predicate, but {number_of_failing} did not actual: {marked_actual} @@ -927,10 +1119,10 @@ where } } -impl Expectation> for NoneSatisfies

+impl Expectation, D> for NoneSatisfies

where - T: Debug, P: FnMut(&T) -> bool, + D: Represent, { fn test(&mut self, subject: &Vec) -> bool { for (i, e) in subject.iter().enumerate() { @@ -946,12 +1138,18 @@ where expression: &Expression<'_>, actual: &Vec, _inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let number_of_failing = self.failing.len(); - let failing = collect_selected_values(&self.failing, actual); - let marked_actual = - mark_selected_items_in_collection(actual, &self.failing, format, mark_unexpected); + let failing = collect_selected_values(&self.failing, actual, representation); + let marked_actual = mark_selected_items_in_collection( + actual, + &self.failing, + representation, + format, + mark_unexpected, + ); format!( r"expected none of the elements of {expression} to satisfy the predicate, but {number_of_failing} did actual: {marked_actual} @@ -960,19 +1158,21 @@ where } } -impl<'a, S, T, R> AssertOrderedElements for Spec<'a, S, R> +impl<'a, S, T, D, R> AssertOrderedElements for Spec<'a, S, D, R> where S: IntoIterator, ::IntoIter: DefinedOrderProperty, - T: Debug, + D: Represent + Clone, R: FailingStrategy, { - type SingleElement = Spec<'a, T, R>; - type MultipleElements = Spec<'a, Vec, R>; + type SingleElement = Spec<'a, T, D, R>; + type MultipleElements = Spec<'a, Vec, D, R>; fn first_element(self) -> Self::SingleElement { + let representation = self.representation().clone(); let spec = self .mapping(Vec::from_iter) + .represented_by(representation.clone()) .expecting(has_at_least_number_of_elements(1)); if spec.has_failures() { PanicOnFail.do_fail_with(&spec.failures()); @@ -980,13 +1180,16 @@ where } let orig_subject_name = spec.expression(); let new_subject_name = format!("the first element of {orig_subject_name}"); - spec.extracting("", |mut collection| collection.remove(0)) + spec.extracting("[first]", |mut collection| collection.remove(0)) .named(new_subject_name) + .represented_by(representation) } fn last_element(self) -> Self::SingleElement { + let representation = self.representation().clone(); let spec = self .mapping(Vec::from_iter) + .represented_by(representation.clone()) .expecting(has_at_least_number_of_elements(1)); if spec.has_failures() { PanicOnFail.do_fail_with(&spec.failures()); @@ -994,18 +1197,21 @@ where } let orig_subject_name = spec.expression(); let new_subject_name = format!("the last element of {orig_subject_name}"); - spec.extracting("", |mut collection| { + spec.extracting("[last]", |mut collection| { collection.pop().unwrap_or_else(|| { unreachable!("Assertion failed and should have panicked! Please report a bug.") }) }) .named(new_subject_name) + .represented_by(representation) } fn nth_element(self, n: usize) -> Self::SingleElement { + let representation = self.representation().clone(); let min_len = n + 1; let spec = self .mapping(Vec::from_iter) + .represented_by(representation.clone()) .expecting(has_at_least_number_of_elements(min_len)); if spec.has_failures() { PanicOnFail.do_fail_with(&spec.failures()); @@ -1013,11 +1219,13 @@ where } let orig_subject_name = spec.expression(); let new_subject_name = format!("{orig_subject_name}[{n}]"); - spec.extracting("", |mut collection| collection.remove(n)) + spec.extracting("[nth]", |mut collection| collection.remove(n)) .named(new_subject_name) + .represented_by(representation) } fn elements_at(self, indices: impl IntoIterator) -> Self::MultipleElements { + let representation = self.representation().clone(); let indices = Vec::from_iter(indices); let orig_subject_name = self.expression(); let new_subject_name = format!("{orig_subject_name} at positions {indices:?}"); @@ -1030,23 +1238,27 @@ where .collect() }) .named(new_subject_name) + .represented_by(representation) } } -impl<'a, S, T, U, R> AssertOrderedElementsRef for Spec<'a, S, R> +impl<'a, S, T, U, D, R> AssertOrderedElementsRef for Spec<'a, S, D, R> where S: IntoIterator, ::IntoIter: DefinedOrderProperty, - T: 'a + ToOwned + Debug, + T: 'a + ToOwned, U: Clone, + D: Represent + Clone, R: FailingStrategy, { - type SingleElement = DerivedSpec<'a, Spec<'a, Vec, R>, U>; - type MultipleElements = DerivedSpec<'a, Spec<'a, Vec, R>, Vec>; + type SingleElement = DerivedSpec<'a, Spec<'a, Vec, D, R>, U, D>; + type MultipleElements = DerivedSpec<'a, Spec<'a, Vec, D, R>, Vec, D>; fn first_element_ref(self) -> Self::SingleElement { + let representation = self.representation().clone(); let original_spec = self .mapping(Vec::from_iter) + .represented_by(representation.clone()) .expecting(has_at_least_number_of_elements(1)); if original_spec.has_failures() { PanicOnFail.do_fail_with(&original_spec.failures()); @@ -1054,17 +1266,19 @@ where } let orig_subject_name = original_spec.expression(); let new_subject_name = format!("the first element of {orig_subject_name}"); - original_spec.extracting_ref("", |collection| + original_spec.extracting_ref("[first]", |collection| collection.first() .unwrap_or_else(|| unreachable!("We should have asserted before, that there is at least one element in the collection/iterator. Please file a bug.") ) - ).named(new_subject_name) + ).named(new_subject_name).represented_by(representation) } fn last_element_ref(self) -> Self::SingleElement { + let representation = self.representation().clone(); let original_spec = self .mapping(Vec::from_iter) + .represented_by(representation.clone()) .expecting(has_at_least_number_of_elements(1)); if original_spec.has_failures() { PanicOnFail.do_fail_with(&original_spec.failures()); @@ -1072,18 +1286,20 @@ where } let orig_subject_name = original_spec.expression(); let new_subject_name = format!("the last element of {orig_subject_name}"); - original_spec.extracting_ref("", |collection| + original_spec.extracting_ref("[last]", |collection| collection.last() .unwrap_or_else(|| unreachable!("We should have asserted before, that there is at least one element in the collection/iterator. Please file a bug.") ) - ).named(new_subject_name) + ).named(new_subject_name).represented_by(representation) } fn nth_element_ref(self, n: usize) -> Self::SingleElement { + let representation = self.representation().clone(); let min_len = n + 1; let original_spec = self .mapping(Vec::from_iter) + .represented_by(representation.clone()) .expecting(has_at_least_number_of_elements(min_len)); if original_spec.has_failures() { PanicOnFail.do_fail_with(&original_spec.failures()); @@ -1091,38 +1307,44 @@ where } let orig_subject_name = original_spec.expression(); let new_subject_name = format!("{orig_subject_name}[{n}]"); - original_spec.extracting_ref("", |collection| + original_spec.extracting_ref("[nth]", |collection| collection.get(n) .unwrap_or_else(|| unreachable!("We should have asserted before, that there is at least one element in the collection/iterator. Please file a bug.") ) - ).named(new_subject_name) + ).named(new_subject_name).represented_by(representation) } fn elements_ref_at(self, indices: impl IntoIterator) -> Self::MultipleElements { + let representation = self.representation().clone(); let indices = Vec::from_iter(indices); let orig_subject_name = self.expression(); let new_subject_name = format!("{orig_subject_name} at positions {indices:?}"); let indices = HashSet::<_>::from_iter(indices); - let original_spec = self.mapping(Vec::from_iter); - original_spec.extracting_ref_iter(new_subject_name, |collection| { - collection - .enumerate() - .filter_map(|(i, e)| { - if indices.contains(&i) { - Some(e.to_owned()) - } else { - None - } - }) - .collect() - }) + let original_spec = self + .mapping(Vec::from_iter) + .represented_by(representation.clone()); + original_spec + .extracting_ref_iter("", |collection| { + collection + .enumerate() + .filter_map(|(i, e)| { + if indices.contains(&i) { + Some(e.to_owned()) + } else { + None + } + }) + .collect() + }) + .named(new_subject_name) + .represented_by(representation) } } -impl Expectation> for HasAtLeastNumberOfElements +impl Expectation, D> for HasAtLeastNumberOfElements where - T: Debug, + D: Represent, { fn test(&mut self, subject: &Vec) -> bool { subject.len() >= self.expected_number_of_elements @@ -1133,36 +1355,68 @@ where expression: &Expression<'_>, actual: &Vec, _inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let actual_length = actual.len(); let actual_elements = match actual_length { - 0 => mark_unexpected_string("no elements", format), - 1 => mark_unexpected_string("one element", format), - _ => mark_unexpected_string(&format!("{actual_length} elements"), format), + 0 => mark_unexpected("no elements", &DisplayRepresentation, format), + 1 => mark_unexpected("one element", &DisplayRepresentation, format), + _ => mark_unexpected( + &format!("{actual_length} elements"), + &DisplayRepresentation, + format, + ), }; let expected_elements = match self.expected_number_of_elements { - 0 => mark_missing_string("no elements", format), - 1 => mark_missing_string("at least one element", format), - _ => mark_missing_string( + 0 => mark_missing("no elements", &DisplayRepresentation, format), + 1 => mark_missing("at least one element", &DisplayRepresentation, format), + _ => mark_missing( &format!("at least {} elements", self.expected_number_of_elements), + &DisplayRepresentation, format, ), }; + let represented_actual = actual + .iter() + .map(|s| Represented::from((s, representation))) + .collect::>(); format!( r"expected {expression} to have {expected_elements}, but has {actual_elements} - actual: {actual:?}" + actual: {represented_actual:?}" ) } } -pub fn collect_selected_values<'a, T>(indices: &HashSet, collection: &'a [T]) -> Vec<&'a T> { +pub fn collect_selected_values<'t, 'd, T, D>( + indices: &HashSet, + collection: &'t [T], + representation: &'d D, +) -> Vec> { + collection + .iter() + .enumerate() + .filter_map(|(idx, value)| { + if indices.contains(&idx) { + Some(Represented::from((value, representation))) + } else { + None + } + }) + .collect() +} + +pub fn collect_selected_ref_values<'t, 'd, T, D>( + indices: &HashSet, + collection: &[&'t T], + representation: &'d D, +) -> Vec> { collection .iter() .enumerate() .filter_map(|(idx, value)| { if indices.contains(&idx) { - Some(value) + Some(Represented::from((*value, representation))) } else { None } diff --git a/src/length.rs b/src/length.rs index bb2f9fd..57c5b4e 100644 --- a/src/length.rs +++ b/src/length.rs @@ -9,15 +9,16 @@ use crate::expectations::{ }; use crate::properties::{IsEmptyProperty, LengthProperty}; use crate::spec::{ - DiffFormat, Expectation, Expecting, Expression, FailingStrategy, Invertible, Spec, + DiffFormat, Expectation, Expecting, Expression, FailingStrategy, Invertible, Represent, + Represented, Spec, }; -use crate::std::fmt::Debug; use crate::std::ops::RangeBounds; use crate::std::{format, string::String}; -impl AssertEmptiness for Spec<'_, S, R> +impl AssertEmptiness for Spec<'_, S, D, R> where - S: IsEmptyProperty + Debug, + S: IsEmptyProperty, + D: Represent, R: FailingStrategy, { fn is_empty(self) -> Self { @@ -29,9 +30,10 @@ where } } -impl Expectation for IsEmpty +impl Expectation for IsEmpty where - S: IsEmptyProperty + Debug, + S: IsEmptyProperty, + D: Represent, { fn test(&mut self, subject: &S) -> bool { subject.is_empty_property() @@ -42,6 +44,7 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let (not, expected) = if inverted { @@ -49,7 +52,7 @@ where } else { ("", "") }; - let marked_actual = mark_unexpected(actual, format); + let marked_actual = mark_unexpected(actual, representation, format); format!( "expected {expression} to be {not}empty\n but was: {marked_actual}\n expected: {expected}" ) @@ -58,9 +61,10 @@ where impl Invertible for IsEmpty {} -impl AssertHasLength for Spec<'_, S, R> +impl AssertHasLength for Spec<'_, S, D, R> where - S: LengthProperty + Debug, + S: LengthProperty, + D: Represent, R: FailingStrategy, { fn has_length(self, expected_length: usize) -> Self { @@ -69,7 +73,8 @@ where fn has_length_in_range(self, expected_range: U) -> Self where - U: RangeBounds + Debug, + U: RangeBounds, + D: Represent, { self.expecting(has_length_in_range(expected_range)) } @@ -91,9 +96,10 @@ where } } -impl Expectation for HasLength +impl Expectation for HasLength where - S: LengthProperty + Debug, + S: LengthProperty, + D: Represent, { fn test(&mut self, subject: &S) -> bool { subject.length_property() == self.expected_length @@ -104,24 +110,26 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let not = if inverted { "not " } else { "" }; - let marked_actual = mark_unexpected(&actual.length_property(), format); - let marked_expected = mark_missing(&self.expected_length, format); + let marked_actual = mark_unexpected(&actual.length_property(), representation, format); + let marked_expected = mark_missing(&self.expected_length, representation, format); + let expected_length = Represented::from((&self.expected_length, representation)); format!( - "expected {expression} to {not}have a length of {}\n but was: {marked_actual}\n expected: {not}{marked_expected}", - self.expected_length, + "expected {expression} to {not}have a length of {expected_length}\n but was: {marked_actual}\n expected: {not}{marked_expected}", ) } } impl Invertible for HasLength {} -impl Expectation for HasLengthInRange +impl Expectation for HasLengthInRange where - S: LengthProperty + Debug, - R: RangeBounds + Debug, + S: LengthProperty, + R: RangeBounds, + D: Represent + Represent, { fn test(&mut self, subject: &S) -> bool { self.expected_range.contains(&subject.length_property()) @@ -132,23 +140,25 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let not = if inverted { "not " } else { "" }; - let marked_actual = mark_unexpected(&actual.length_property(), format); - let marked_expected = mark_missing(&self.expected_range, format); + let marked_actual = mark_unexpected(&actual.length_property(), representation, format); + let marked_expected = mark_missing(&self.expected_range, representation, format); + let expected_range = Represented::from((&self.expected_range, representation)); format!( - "expected {expression} to {not}have a length within range {:?}\n but was: {marked_actual}\n expected: {not}{marked_expected}", - self.expected_range, + "expected {expression} to {not}have a length within range {expected_range:?}\n but was: {marked_actual}\n expected: {not}{marked_expected}", ) } } impl Invertible for HasLengthInRange {} -impl Expectation for HasLengthLessThan +impl Expectation for HasLengthLessThan where - S: LengthProperty + Debug, + S: LengthProperty, + D: Represent, { fn test(&mut self, subject: &S) -> bool { subject.length_property() < self.expected_length @@ -159,23 +169,25 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let (not, cmp) = if inverted { ("not ", ">=") } else { ("", "<") }; - let marked_actual = mark_unexpected(&actual.length_property(), format); - let marked_expected = mark_missing(&self.expected_length, format); + let marked_actual = mark_unexpected(&actual.length_property(), representation, format); + let marked_expected = mark_missing(&self.expected_length, representation, format); + let expected_length = Represented::from((&self.expected_length, representation)); format!( - "expected {expression} to {not}have a length less than {:?}\n but was: {marked_actual}\n expected: {cmp} {marked_expected}", - self.expected_length, + "expected {expression} to {not}have a length less than {expected_length:?}\n but was: {marked_actual}\n expected: {cmp} {marked_expected}", ) } } impl Invertible for HasLengthLessThan {} -impl Expectation for HasLengthGreaterThan +impl Expectation for HasLengthGreaterThan where - S: LengthProperty + Debug, + S: LengthProperty, + D: Represent, { fn test(&mut self, subject: &S) -> bool { subject.length_property() > self.expected_length @@ -186,23 +198,25 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let (not, cmp) = if inverted { ("not ", "<=") } else { ("", ">") }; - let marked_actual = mark_unexpected(&actual.length_property(), format); - let marked_expected = mark_missing(&self.expected_length, format); + let marked_actual = mark_unexpected(&actual.length_property(), representation, format); + let marked_expected = mark_missing(&self.expected_length, representation, format); + let expected_length = Represented::from((&self.expected_length, representation)); format!( - "expected {expression} to {not}have a length greater than {:?}\n but was: {marked_actual}\n expected: {cmp} {marked_expected}", - self.expected_length, + "expected {expression} to {not}have a length greater than {expected_length:?}\n but was: {marked_actual}\n expected: {cmp} {marked_expected}", ) } } impl Invertible for HasLengthGreaterThan {} -impl Expectation for HasAtMostLength +impl Expectation for HasAtMostLength where - S: LengthProperty + Debug, + S: LengthProperty, + D: Represent, { fn test(&mut self, subject: &S) -> bool { subject.length_property() <= self.expected_length @@ -213,23 +227,25 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let (not, cmp) = if inverted { ("not ", ">") } else { ("", "<=") }; - let marked_actual = mark_unexpected(&actual.length_property(), format); - let marked_expected = mark_missing(&self.expected_length, format); + let marked_actual = mark_unexpected(&actual.length_property(), representation, format); + let marked_expected = mark_missing(&self.expected_length, representation, format); + let expected_length = Represented::from((&self.expected_length, representation)); format!( - "expected {expression} to {not}have at most a length of {:?}\n but was: {marked_actual}\n expected: {cmp} {marked_expected}", - self.expected_length, + "expected {expression} to {not}have at most a length of {expected_length:?}\n but was: {marked_actual}\n expected: {cmp} {marked_expected}", ) } } impl Invertible for HasAtMostLength {} -impl Expectation for HasAtLeastLength +impl Expectation for HasAtLeastLength where - S: LengthProperty + Debug, + S: LengthProperty, + D: Represent, { fn test(&mut self, subject: &S) -> bool { subject.length_property() >= self.expected_length @@ -240,14 +256,15 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let (not, cmp) = if inverted { ("not ", "<") } else { ("", ">=") }; - let marked_actual = mark_unexpected(&actual.length_property(), format); - let marked_expected = mark_missing(&self.expected_length, format); + let marked_actual = mark_unexpected(&actual.length_property(), representation, format); + let marked_expected = mark_missing(&self.expected_length, representation, format); + let expected_length = Represented::from((&self.expected_length, representation)); format!( - "expected {expression} to {not}have at least a length of {:?}\n but was: {marked_actual}\n expected: {cmp} {marked_expected}", - self.expected_length, + "expected {expression} to {not}have at least a length of {expected_length:?}\n but was: {marked_actual}\n expected: {cmp} {marked_expected}", ) } } diff --git a/src/lib.rs b/src/lib.rs index 9446ced..b127ec5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -630,7 +630,7 @@ //! an expectation that verifies that a value of type `Either` is a left value. //! //! ```no_run -//! use asserting::spec::{DiffFormat, Expectation, Expression, Unknown}; +//! use asserting::spec::{DiffFormat, Expectation, Expression, Represent, Represented, Unknown}; //! use std::fmt::Debug; //! //! #[derive(Debug)] @@ -641,10 +641,9 @@ //! //! struct IsLeft; //! -//! impl Expectation> for IsLeft +//! impl Expectation, D> for IsLeft //! where -//! L: Debug, -//! R: Debug, +//! D: Represent + Represent, //! { //! fn test(&mut self, subject: &Either) -> bool { //! match subject { @@ -653,9 +652,20 @@ //! } //! } //! -//! fn message(&self, expression: &Expression<'_>, actual: &Either, _inverted: bool, _format: &DiffFormat) -> String { +//! fn message( +//! &self, +//! expression: &Expression<'_>, +//! actual: &Either, +//! _inverted: bool, +//! representation: &D, +//! _format: &DiffFormat +//! ) -> String { +//! let represented_actual = match actual { +//! Either::Left(left) => format!("Left({:?})", Represented::from((left, representation))), +//! Either::Right(right) => format!("Right({:?})", Represented::from((right, representation))), +//! }; //! format!( -//! "expected {expression} is {:?}\n but was: {actual:?}\n expected: {:?}", +//! "expected {expression} is {:?}\n but was: {represented_actual:?}\n expected: {:?}", //! Either::Left::<_, Unknown>(Unknown), //! Either::Left::<_, Unknown>(Unknown), //! ) @@ -667,7 +677,7 @@ //! method: //! //! ``` -//! # use asserting::spec::{DiffFormat, Expectation, Expression, Unknown}; +//! # use asserting::spec::{DiffFormat, Expectation, Expression, Represent, Represented, Unknown}; //! # use std::fmt::Debug; //! # //! # #[derive(Debug)] @@ -678,10 +688,9 @@ //! # //! # struct IsLeft; //! # -//! # impl Expectation> for IsLeft +//! # impl Expectation, D> for IsLeft //! # where -//! # L: Debug, -//! # R: Debug, +//! # D: Represent + Represent, //! # { //! # fn test(&mut self, subject: &Either) -> bool { //! # match subject { @@ -690,9 +699,20 @@ //! # } //! # } //! # -//! # fn message(&self, expression: &Expression<'_>, actual: &Either, _inverted: bool, _format: &DiffFormat) -> String { +//! # fn message( +//! # &self, +//! # expression: &Expression<'_>, +//! # actual: &Either, +//! # _inverted: bool, +//! # representation: &D, +//! # _format: &DiffFormat +//! # ) -> String { +//! # let represented_actual = match actual { +//! # Either::Left(left) => format!("Left({:?})", Represented::from((left, representation))), +//! # Either::Right(right) => format!("Right({:?})", Represented::from((right, representation))), +//! # }; //! # format!( -//! # "expected {expression} is {:?}\n but was: {actual:?}\n expected: {:?}", +//! # "expected {expression} is {:?}\n but was: {represented_actual:?}\n expected: {:?}", //! # Either::Left::<_, Unknown>(Unknown), //! # Either::Left::<_, Unknown>(Unknown), //! # ) @@ -715,7 +735,7 @@ //! trait. //! //! ``` -//! # use asserting::spec::{DiffFormat, Expectation, Expression, Unknown}; +//! # use asserting::spec::{DiffFormat, Expectation, Expression, Represent, Represented, Unknown}; //! # //! # #[derive(Debug)] //! # enum Either { @@ -725,10 +745,9 @@ //! # //! # struct IsLeft; //! # -//! # impl Expectation> for IsLeft +//! # impl Expectation, D> for IsLeft //! # where -//! # L: Debug, -//! # R: Debug, +//! # D: Represent + Represent, //! # { //! # fn test(&mut self, subject: &Either) -> bool { //! # match subject { @@ -737,25 +756,34 @@ //! # } //! # } //! # -//! # fn message(&self, expression: &Expression<'_>, actual: &Either, _inverted: bool, _format: &DiffFormat) -> String { +//! # fn message( +//! # &self, +//! # expression: &Expression<'_>, +//! # actual: &Either, +//! # _inverted: bool, +//! # representation: &D, +//! # _format: &DiffFormat +//! # ) -> String { +//! # let represented_actual = match actual { +//! # Either::Left(left) => format!("Left({:?})", Represented::from((left, representation))), +//! # Either::Right(right) => format!("Right({:?})", Represented::from((right, representation))), +//! # }; //! # format!( -//! # "expected {expression} is {:?}\n but was: {actual:?}\n expected: {:?}", +//! # "expected {expression} is {:?}\n but was: {represented_actual:?}\n expected: {:?}", //! # Either::Left::<_, Unknown>(Unknown), //! # Either::Left::<_, Unknown>(Unknown), //! # ) //! # } //! # } //! use asserting::spec::{Expecting, FailingStrategy, Spec}; -//! use std::fmt::Debug; //! //! pub trait AssertEither { //! fn is_left(self) -> Self; //! } //! -//! impl AssertEither for Spec<'_, Either, Q> +//! impl AssertEither for Spec<'_, Either, D, Q> //! where -//! L: Debug, -//! R: Debug, +//! D: Represent + Represent, //! Q: FailingStrategy, //! { //! fn is_left(self) -> Self { @@ -768,8 +796,7 @@ //! subject of type `Either` is a left value. //! //! ``` -//! # use asserting::spec::{DiffFormat, Expectation, Expression, Unknown}; -//! # use std::fmt::Debug; +//! # use asserting::spec::{DiffFormat, Expectation, Expression, Represent, Represented, Unknown}; //! # //! # #[derive(Debug)] //! # enum Either { @@ -779,10 +806,9 @@ //! # //! # struct IsLeft; //! # -//! # impl Expectation> for IsLeft +//! # impl Expectation, D> for IsLeft //! # where -//! # L: Debug, -//! # R: Debug, +//! # D: Represent + Represent, //! # { //! # fn test(&mut self, subject: &Either) -> bool { //! # match subject { @@ -791,9 +817,20 @@ //! # } //! # } //! # -//! # fn message(&self, expression: &Expression<'_>, actual: &Either, _inverted: bool, _format: &DiffFormat) -> String { +//! # fn message( +//! # &self, +//! # expression: &Expression<'_>, +//! # actual: &Either, +//! # _inverted: bool, +//! # representation: &D, +//! # _format: &DiffFormat +//! # ) -> String { +//! # let represented_actual = match actual { +//! # Either::Left(left) => format!("Left({:?})", Represented::from((left, representation))), +//! # Either::Right(right) => format!("Right({:?})", Represented::from((right, representation))), +//! # }; //! # format!( -//! # "expected {expression} is {:?}\n but was: {actual:?}\n expected: {:?}", +//! # "expected {expression} is {:?}\n but was: {represented_actual:?}\n expected: {:?}", //! # Either::Left::<_, Unknown>(Unknown), //! # Either::Left::<_, Unknown>(Unknown), //! # ) @@ -805,10 +842,9 @@ //! # fn is_left(self) -> Self; //! # } //! # -//! # impl AssertEither for Spec<'_, Either, Q> +//! # impl AssertEither for Spec<'_, Either, D, Q> //! # where -//! # L: Debug, -//! # R: Debug, +//! # D: Represent + Represent, //! # Q: FailingStrategy, //! # { //! # fn is_left(self) -> Self { @@ -847,7 +883,7 @@ //! //! // we implement the trait for a generic `S: Borrow` so that the //! // assertion method can be called on an owned or borrowed `Person` instance -//! impl<'a, S, R> AssertOver18 for Spec<'a, S, R> +//! impl<'a, S, D, R> AssertOver18 for Spec<'a, S, D, R> //! where //! S: Borrow, //! R: FailingStrategy, @@ -869,11 +905,128 @@ //! assert_that!(person).is_over_18(); //! ``` //! +//! # Type formatting (aka Representation) +//! +//! `asserting` uses a representation mechanism to control how values of +//! different types are formatted in the failure report of failing assertions. +//! +//! By default, the formatting of values is delegated to the `fmt`-method of the +//! [`std::fmt::Debug`] trait. We can write assertions for any type that +//! implements `std::fmt::Debug`. In failure reports the subject and the +//! expected value are formatted by the implementation of the `Debug`-trait. +//! This works well for most cases, but there are two situations where we need +//! a more flexible mechanism: +//! +//! 1. asserting the value of a type that does not implement `std::fmt::Debug` +//! 2. custom formatting, without changing the `std::fmt::Debug` implementation +//! +//! With the representation mechanism, we implement custom formatting of values +//! of any type, including foreign types in other crates. The representation +//! mechanism is based on the [`Represent`] trait. Its `represent` method has a +//! similar signature as the `fmt`-method of the `Debug` and `Display` traits in +//! the standard library. +//! +//! If a type already implements [`Debug`], we can use an ad-hoc representation +//! or a representation struct as well. This is useful when we want to get +//! custom formatted values in failure reports of failing assertions. +//! +//! ## Ad-hoc representation +//! +//! Let's have a look at an example. We want to write an assertion for a type +//! `Foo` that does not implement `std::fmt::Debug`. Now we have two options. +//! Either specify a so-called ad-hoc representation or implement a custom +//! representation. +//! +//! The ad-hoc representation is just a function or closure with a signature +//! similar to the `fmt`-method of the `Debug`-trait. When writing an assertion, +//! we configure the [`Spec`] to use the ad-hoc representation by calling the +//! `represented_as` method. +//! +//! ``` +//! use asserting::prelude::*; +//! +//! #[derive(PartialEq)] +//! struct Foo { +//! bar: String, +//! baz: u16, +//! } +//! +//! let foo = Foo { bar: "bar".into(), baz: 42 }; +//! +//! assert_that!(foo) +//! .represented_as(|val, f| write!(f, "Foo {{ bar: {}, baz: {} }}", val.bar, val.baz)) +//! .is_equal_to(Foo { bar: "bar".into(), baz: 42 }); +//! ``` +//! +//! We can also write a function that formats a value of type `Foo` and hand-in +//! this function in the call to `represented_as`: +//! +//! ``` +//! use asserting::prelude::*; +//! use core::fmt; +//! +//! #[derive(PartialEq)] +//! struct Foo { +//! bar: String, +//! baz: u16, +//! } +//! +//! fn represent_foo(value: &Foo, f: &mut fmt::Formatter<'_>) -> fmt::Result { +//! write!(f, "Foo {{ bar: {}, baz: {} }}", value.bar, value.baz) +//! } +//! +//! let foo = Foo { bar: "bar".into(), baz: 42 }; +//! +//! assert_that!(foo) +//! .represented_as(represent_foo) +//! .is_equal_to(Foo { bar: "bar".into(), baz: 42 }); +//! ``` +//! +//! ## Representation struct +//! +//! The second option is to define a representation struct and implement the +//! [`Represent`] trait for the type `Foo` on this representation struct. When +//! writing the assertion, we hand-in the representation struct to the +//! `represented_by` method: +//! +//! ``` +//! use asserting::prelude::*; +//! use core::fmt; +//! +//! #[derive(PartialEq)] +//! struct Foo { +//! bar: String, +//! baz: u16, +//! } +//! +//! #[derive(Clone, Copy)] +//! struct FooRepresentation; +//! +//! impl Represent for FooRepresentation { +//! fn represent(&self, value: &Foo, f: &mut fmt::Formatter<'_>) -> fmt::Result { +//! write!(f, "Foo {{ bar: {}, baz: {} }}", value.bar, value.baz) +//! } +//! } +//! +//! let foo = Foo { bar: "bar".into(), baz: 42 }; +//! +//! assert_that(foo) +//! .represented_by(FooRepresentation) +//! .is_equal_to(Foo { bar: "bar".into(), baz: 42 }); +//! ``` +//! +//! In most cases the representation struct will be just a unit struct like in +//! our example. It is recommended to derive `Clone` for the representation +//! struct. When asserting values in container types like `Vec` or +//! `Option`, it is required that the representation struct implements +//! `Clone`. +//! //! [`AssertElements`]: assertions::AssertElements //! [`AssertFilteredElements`]: assertions::AssertFilteredElements //! [`AssertFailure`]: spec::AssertFailure //! [`Expectation`]: spec::Expectation //! [`LengthProperty`]: properties::LengthProperty +//! [`Represent`]: spec::Represent //! [`Spec`]: spec::Spec //! [`Spec::expecting()`]: spec::Expecting::expecting //! [`Spec::satisfies()`]: spec::Satisfies::satisfies @@ -908,6 +1061,11 @@ mod std { pub use core::borrow::*; } + pub mod boxed { + extern crate alloc; + pub use alloc::boxed::*; + } + pub mod fmt { extern crate alloc; pub use alloc::fmt::*; @@ -992,6 +1150,7 @@ mod os_sting; mod panic; mod predicate; mod range; +mod representation; mod result; #[cfg(feature = "rust-decimal")] mod rust_decimal; diff --git a/src/map/mod.rs b/src/map/mod.rs index 4975a20..d7f271d 100644 --- a/src/map/mod.rs +++ b/src/map/mod.rs @@ -1,7 +1,7 @@ use crate::assertions::{AssertMapContainsKey, AssertMapContainsValue}; use crate::colored::{ mark_all_entries_in_map, mark_missing, mark_selected_entries_in_map, - mark_selected_items_in_collection, mark_unexpected_string, + mark_selected_items_in_collection, mark_unexpected, }; use crate::expectations::{ MapContainsExactlyKeys, MapContainsKey, MapContainsKeys, MapContainsValue, MapContainsValues, @@ -9,23 +9,25 @@ use crate::expectations::{ map_contains_keys, map_contains_value, map_contains_values, map_does_not_contain_keys, map_does_not_contain_values, not, }; -use crate::iterator::collect_selected_values; +use crate::iterator::{collect_selected_ref_values, collect_selected_values}; use crate::properties::MapProperties; use crate::spec::{ - DiffFormat, Expectation, Expecting, Expression, FailingStrategy, Invertible, Spec, + DiffFormat, Expectation, Expecting, Expression, FailingStrategy, Invertible, Represent, + Represented, Spec, }; -use crate::std::fmt::Debug; use crate::std::format; use crate::std::string::String; use crate::std::vec::Vec; use hashbrown::HashSet; -impl AssertMapContainsKey for Spec<'_, S, R> +impl AssertMapContainsKey for Spec<'_, S, D, R> where - S: MapProperties + Debug, - ::Key: PartialEq + Debug, - ::Value: Debug, - E: Debug, + S: MapProperties, + ::Key: PartialEq, + D: Represent + + Represent + + Represent<::Key> + + Represent<::Value>, R: FailingStrategy, { fn contains_key(self, expected_key: E) -> Self { @@ -49,12 +51,11 @@ where } } -impl Expectation for MapContainsKey +impl Expectation for MapContainsKey where M: MapProperties, - ::Key: PartialEq + Debug, - ::Value: Debug, - E: Debug, + ::Key: PartialEq, + D: Represent + Represent<::Key> + Represent<::Value>, { fn test(&mut self, subject: &M) -> bool { subject.keys_property().any(|k| k == &self.expected_key) @@ -65,9 +66,9 @@ where expression: &Expression<'_>, actual: &M, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { - let expected_key = &self.expected_key; let actual_entries: Vec<_> = actual.entries_property().collect(); let (not, marked_actual) = if inverted { let found: HashSet = actual_entries @@ -84,16 +85,18 @@ where let selected_entries_marked = mark_selected_entries_in_map( &actual_entries, &found, + representation, format, - mark_unexpected_string, + mark_unexpected, ); ("not ", selected_entries_marked) } else { let all_entries_marked = - mark_all_entries_in_map(&actual_entries, format, mark_unexpected_string); + mark_all_entries_in_map(&actual_entries, representation, format, mark_unexpected); ("", all_entries_marked) }; - let marked_expected = mark_missing(&self.expected_key, format); + let marked_expected = mark_missing(&self.expected_key, representation, format); + let expected_key = Represented::from((&self.expected_key, representation)); format!( "expected {expression} to {not}contain the key {expected_key:?}\n but was: {marked_actual}\n expected: {not}{marked_expected}" ) @@ -102,12 +105,11 @@ where impl Invertible for MapContainsKey {} -impl Expectation for MapContainsKeys +impl Expectation for MapContainsKeys where M: MapProperties, - ::Key: PartialEq + Debug, - ::Value: Debug, - E: Debug, + ::Key: PartialEq, + D: Represent + Represent<::Key> + Represent<::Value>, { fn test(&mut self, subject: &M) -> bool { let keys = subject.keys_property().collect::>(); @@ -125,6 +127,7 @@ where expression: &Expression<'_>, actual: &M, _inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let expected_keys = &self.expected_keys; @@ -142,12 +145,23 @@ where let marked_actual = mark_selected_entries_in_map( &actual_entries, &extra_entries, + representation, + format, + mark_unexpected, + ); + let marked_expected = mark_selected_items_in_collection( + expected_keys, + missing, + representation, format, - mark_unexpected_string, + mark_missing, ); - let marked_expected = - mark_selected_items_in_collection(expected_keys, missing, format, mark_missing); - let missing_keys = collect_selected_values(missing, expected_keys); + let missing_keys = collect_selected_values(missing, expected_keys, representation); + let expected_keys = self + .expected_keys + .iter() + .map(|k| Represented::from((k, representation))) + .collect::>(); format!( r"expected {expression} to contain the keys {expected_keys:?} @@ -158,12 +172,11 @@ where } } -impl Expectation for MapDoesNotContainKeys +impl Expectation for MapDoesNotContainKeys where M: MapProperties, - ::Key: PartialEq + Debug, - ::Value: Debug, - E: Debug, + ::Key: PartialEq, + D: Represent + Represent<::Key> + Represent<::Value>, { fn test(&mut self, subject: &M) -> bool { let keys = subject.keys_property().collect::>(); @@ -181,6 +194,7 @@ where expression: &Expression<'_>, actual: &M, _inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let expected_keys = &self.expected_keys; @@ -193,11 +207,26 @@ where found.insert(actual_index); } } - let marked_actual = - mark_selected_entries_in_map(&actual_entries, &found, format, mark_unexpected_string); - let marked_expected = - mark_selected_items_in_collection(expected_keys, extra, format, mark_missing); - let extra_keys = collect_selected_values(&found, &actual_keys); + let marked_actual = mark_selected_entries_in_map( + &actual_entries, + &found, + representation, + format, + mark_unexpected, + ); + let marked_expected = mark_selected_items_in_collection( + expected_keys, + extra, + representation, + format, + mark_missing, + ); + let extra_keys = collect_selected_ref_values(&found, &actual_keys, representation); + let expected_keys = self + .expected_keys + .iter() + .map(|k| Represented::from((k, representation))) + .collect::>(); format!( r"expected {expression} to not contain the keys {expected_keys:?} @@ -208,12 +237,11 @@ where } } -impl Expectation for MapContainsExactlyKeys +impl Expectation for MapContainsExactlyKeys where M: MapProperties, - ::Key: PartialEq + Debug, - ::Value: Debug, - E: Debug, + ::Key: PartialEq, + D: Represent + Represent<::Key> + Represent<::Value>, { fn test(&mut self, subject: &M) -> bool { let actual_keys = subject.keys_property().collect::>(); @@ -236,6 +264,7 @@ where expression: &Expression<'_>, actual: &M, _inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let expected_keys = &self.expected_keys; @@ -244,12 +273,27 @@ where let actual_entries: Vec<_> = actual.entries_property().collect(); let actual_keys: Vec<_> = actual.keys_property().collect(); - let marked_actual = - mark_selected_entries_in_map(&actual_entries, extra, format, mark_unexpected_string); - let marked_expected = - mark_selected_items_in_collection(expected_keys, missing, format, mark_missing); - let missing_keys = collect_selected_values(missing, expected_keys); - let extra_keys = collect_selected_values(extra, &actual_keys); + let marked_actual = mark_selected_entries_in_map( + &actual_entries, + extra, + representation, + format, + mark_unexpected, + ); + let marked_expected = mark_selected_items_in_collection( + expected_keys, + missing, + representation, + format, + mark_missing, + ); + let missing_keys = collect_selected_values(missing, expected_keys, representation); + let extra_keys = collect_selected_ref_values(extra, &actual_keys, representation); + let expected_keys = self + .expected_keys + .iter() + .map(|e| Represented::from((e, representation))) + .collect::>(); format!( r"expected {expression} to contain exactly the keys {expected_keys:?} @@ -261,12 +305,11 @@ where } } -impl AssertMapContainsValue for Spec<'_, S, R> +impl AssertMapContainsValue for Spec<'_, S, D, R> where S: MapProperties, - ::Key: Debug, - ::Value: PartialEq + Debug, - E: Debug, + ::Value: PartialEq, + D: Represent + Represent<::Key> + Represent<::Value>, R: FailingStrategy, { fn contains_value(self, expected_value: E) -> Self { @@ -286,12 +329,11 @@ where } } -impl Expectation for MapContainsValue +impl Expectation for MapContainsValue where M: MapProperties, - ::Key: Debug, - ::Value: PartialEq + Debug, - E: Debug, + ::Value: PartialEq, + D: Represent + Represent<::Key> + Represent<::Value>, { fn test(&mut self, subject: &M) -> bool { subject.values_property().any(|v| v == &self.expected_value) @@ -302,9 +344,9 @@ where expression: &Expression<'_>, actual: &M, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { - let expected_value = &self.expected_value; let actual_entries: Vec<_> = actual.entries_property().collect(); let (not, marked_actual) = if inverted { let found: HashSet = actual_entries @@ -321,16 +363,18 @@ where let selected_entries_marked = mark_selected_entries_in_map( &actual_entries, &found, + representation, format, - mark_unexpected_string, + mark_unexpected, ); ("not ", selected_entries_marked) } else { let all_entries_marked = - mark_all_entries_in_map(&actual_entries, format, mark_unexpected_string); + mark_all_entries_in_map(&actual_entries, representation, format, mark_unexpected); ("", all_entries_marked) }; - let marked_expected = mark_missing(&self.expected_value, format); + let marked_expected = mark_missing(&self.expected_value, representation, format); + let expected_value = Represented::from((&self.expected_value, representation)); format!( "expected {expression} to {not}contain the value {expected_value:?}\n but was: {marked_actual}\n expected: {not}{marked_expected}" @@ -340,12 +384,11 @@ where impl Invertible for MapContainsValue {} -impl Expectation for MapContainsValues +impl Expectation for MapContainsValues where M: MapProperties, - ::Key: Debug, - ::Value: PartialEq + Debug, - E: Debug, + ::Value: PartialEq, + D: Represent + Represent<::Key> + Represent<::Value>, { fn test(&mut self, subject: &M) -> bool { let values = subject.values_property().collect::>(); @@ -363,6 +406,7 @@ where expression: &Expression<'_>, actual: &M, _inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let expected_values = &self.expected_values; @@ -380,12 +424,23 @@ where let marked_actual = mark_selected_entries_in_map( &actual_entries, &extra_entries, + representation, format, - mark_unexpected_string, + mark_unexpected, ); - let marked_expected = - mark_selected_items_in_collection(expected_values, missing, format, mark_missing); - let missing_values = collect_selected_values(missing, expected_values); + let marked_expected = mark_selected_items_in_collection( + expected_values, + missing, + representation, + format, + mark_missing, + ); + let missing_values = collect_selected_values(missing, expected_values, representation); + let expected_values = self + .expected_values + .iter() + .map(|e| Represented::from((e, representation))) + .collect::>(); format!( r"expected {expression} to contain the values {expected_values:?} @@ -396,12 +451,11 @@ where } } -impl Expectation for MapDoesNotContainValues +impl Expectation for MapDoesNotContainValues where M: MapProperties, - ::Key: Debug, - ::Value: PartialEq + Debug, - E: Debug, + ::Value: PartialEq, + D: Represent + Represent<::Key> + Represent<::Value>, { fn test(&mut self, subject: &M) -> bool { let values = subject.values_property().collect::>(); @@ -419,6 +473,7 @@ where expression: &Expression<'_>, actual: &M, _inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let expected_values = &self.expected_values; @@ -434,11 +489,26 @@ where found.insert(actual_index); } } - let marked_actual = - mark_selected_entries_in_map(&actual_entries, &found, format, mark_unexpected_string); - let marked_expected = - mark_selected_items_in_collection(expected_values, extra, format, mark_missing); - let extra_values = collect_selected_values(&found, &actual_values); + let marked_actual = mark_selected_entries_in_map( + &actual_entries, + &found, + representation, + format, + mark_unexpected, + ); + let marked_expected = mark_selected_items_in_collection( + expected_values, + extra, + representation, + format, + mark_missing, + ); + let extra_values = collect_selected_ref_values(&found, &actual_values, representation); + let expected_values = self + .expected_values + .iter() + .map(|e| Represented::from((e, representation))) + .collect::>(); format!( r"expected {expression} to not contain the values {expected_values:?} diff --git a/src/mapping.rs b/src/mapping.rs index 78d241e..7f09253 100644 --- a/src/mapping.rs +++ b/src/mapping.rs @@ -1,15 +1,15 @@ use crate::assertions::{AssertDebugString, AssertDisplayString}; -use crate::spec::{FailingStrategy, Spec}; +use crate::spec::{DebugRepresentation, FailingStrategy, Spec}; use crate::std::fmt::{Debug, Display}; use crate::std::format; use crate::std::string::{String, ToString}; -impl<'a, S, R> AssertDebugString for Spec<'a, S, R> +impl<'a, S, D, R> AssertDebugString for Spec<'a, S, D, R> where S: Debug, R: FailingStrategy, { - type DebugString = Spec<'a, String, R>; + type DebugString = Spec<'a, String, DebugRepresentation, R>; fn debug_string(self) -> Self::DebugString { let expression_debug_string = format!("{}'s debug string", self.expression()); @@ -18,12 +18,12 @@ where } } -impl<'a, S, R> AssertDisplayString for Spec<'a, S, R> +impl<'a, S, D, R> AssertDisplayString for Spec<'a, S, D, R> where S: Display, R: FailingStrategy, { - type DisplayString = Spec<'a, String, R>; + type DisplayString = Spec<'a, String, DebugRepresentation, R>; fn display_string(self) -> Self::DisplayString { let expression_display_string = format!("{}'s display string", self.expression()); diff --git a/src/number.rs b/src/number.rs index 15e0365..317e9a3 100644 --- a/src/number.rs +++ b/src/number.rs @@ -3,7 +3,7 @@ use crate::assertions::{ AssertDecimalNumber, AssertInfinity, AssertNotANumber, AssertNumericIdentity, AssertSignum, }; -use crate::colored::{mark_missing, mark_missing_string, mark_unexpected}; +use crate::colored::{mark_missing, mark_unexpected}; use crate::expectations::{ HasPrecisionOf, HasScaleOf, IsANumber, IsFinite, IsInfinite, IsInteger, IsNegative, IsOne, IsPositive, IsZero, has_precision_of, has_scale_of, is_a_number, is_finite, is_infinite, @@ -14,15 +14,16 @@ use crate::properties::{ MultiplicativeIdentityProperty, SignumProperty, }; use crate::spec::{ - DiffFormat, Expectation, Expecting, Expression, FailingStrategy, Invertible, Spec, + DiffFormat, DisplayRepresentation, Expectation, Expecting, Expression, FailingStrategy, + Invertible, Represent, Represented, Spec, }; -use crate::std::fmt::Debug; use crate::std::format; use crate::std::string::String; -impl AssertSignum for Spec<'_, S, R> +impl AssertSignum for Spec<'_, S, D, R> where - S: SignumProperty + Debug, + S: SignumProperty, + D: Represent, R: FailingStrategy, { fn is_negative(self) -> Self { @@ -42,9 +43,10 @@ where } } -impl Expectation for IsNegative +impl Expectation for IsNegative where - S: SignumProperty + Debug, + S: SignumProperty, + D: Represent, { fn test(&mut self, subject: &S) -> bool { subject.is_negative_property() @@ -55,6 +57,7 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let (not, expected) = if inverted { @@ -62,8 +65,8 @@ where } else { ("", "< 0") }; - let marked_actual = mark_unexpected(actual, format); - let marked_expected = mark_missing_string(expected, format); + let marked_actual = mark_unexpected(actual, representation, format); + let marked_expected = mark_missing(expected, &DisplayRepresentation, format); format!( "expected {expression} to be {not}negative\n but was: {marked_actual}\n expected: {marked_expected}" ) @@ -72,9 +75,10 @@ where impl Invertible for IsNegative {} -impl Expectation for IsPositive +impl Expectation for IsPositive where - S: SignumProperty + Debug, + S: SignumProperty, + D: Represent, { fn test(&mut self, subject: &S) -> bool { subject.is_positive_property() @@ -85,6 +89,7 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let (not, expected) = if inverted { @@ -92,8 +97,8 @@ where } else { ("", "> 0") }; - let marked_actual = mark_unexpected(actual, format); - let marked_expected = mark_missing_string(expected, format); + let marked_actual = mark_unexpected(actual, representation, format); + let marked_expected = mark_missing(expected, &DisplayRepresentation, format); format!( "expected {expression} to be {not}positive\n but was: {marked_actual}\n expected: {marked_expected}" ) @@ -102,9 +107,10 @@ where impl Invertible for IsPositive {} -impl AssertNumericIdentity for Spec<'_, S, R> +impl AssertNumericIdentity for Spec<'_, S, D, R> where - S: AdditiveIdentityProperty + MultiplicativeIdentityProperty + PartialEq + Debug, + S: AdditiveIdentityProperty + MultiplicativeIdentityProperty + PartialEq, + D: Represent, R: FailingStrategy, { fn is_zero(self) -> Self { @@ -116,9 +122,10 @@ where } } -impl Expectation for IsZero +impl Expectation for IsZero where - S: AdditiveIdentityProperty + PartialEq + Debug, + S: AdditiveIdentityProperty + PartialEq, + D: Represent, { fn test(&mut self, subject: &S) -> bool { *subject == ::additive_identity() @@ -129,11 +136,12 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let not = if inverted { "not " } else { "" }; - let marked_actual = mark_unexpected(&actual, format); - let marked_expected = mark_missing(&S::additive_identity(), format); + let marked_actual = mark_unexpected(actual, representation, format); + let marked_expected = mark_missing(&S::additive_identity(), representation, format); format!( "expected {expression} to be {not}zero\n but was: {marked_actual}\n expected: {not}{marked_expected}" ) @@ -142,9 +150,10 @@ where impl Invertible for IsZero {} -impl Expectation for IsOne +impl Expectation for IsOne where - S: MultiplicativeIdentityProperty + PartialEq + Debug, + S: MultiplicativeIdentityProperty + PartialEq, + D: Represent, { fn test(&mut self, subject: &S) -> bool { *subject == ::multiplicative_identity() @@ -155,11 +164,12 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let not = if inverted { "not " } else { "" }; - let marked_actual = mark_unexpected(actual, format); - let marked_expected = mark_missing(&S::multiplicative_identity(), format); + let marked_actual = mark_unexpected(actual, representation, format); + let marked_expected = mark_missing(&S::multiplicative_identity(), representation, format); format!( "expected {expression} to be {not}one\n but was: {marked_actual}\n expected: {not}{marked_expected}" ) @@ -168,9 +178,10 @@ where impl Invertible for IsOne {} -impl AssertInfinity for Spec<'_, S, R> +impl AssertInfinity for Spec<'_, S, D, R> where - S: InfinityProperty + Debug, + S: InfinityProperty, + D: Represent, R: FailingStrategy, { fn is_infinite(self) -> Self { @@ -182,9 +193,10 @@ where } } -impl Expectation for IsFinite +impl Expectation for IsFinite where - S: InfinityProperty + Debug, + S: InfinityProperty, + D: Represent, { fn test(&mut self, subject: &S) -> bool { subject.is_finite_property() @@ -195,6 +207,7 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let (not, expected) = if inverted { @@ -202,8 +215,8 @@ where } else { ("", "a finite number") }; - let marked_actual = mark_unexpected(actual, format); - let marked_expected = mark_missing_string(expected, format); + let marked_actual = mark_unexpected(actual, representation, format); + let marked_expected = mark_missing(expected, &DisplayRepresentation, format); format!( "expected {expression} to be {not}finite\n but was: {marked_actual}\n expected: {marked_expected}" ) @@ -212,9 +225,10 @@ where impl Invertible for IsFinite {} -impl Expectation for IsInfinite +impl Expectation for IsInfinite where - S: InfinityProperty + Debug, + S: InfinityProperty, + D: Represent, { fn test(&mut self, subject: &S) -> bool { subject.is_infinite_property() @@ -225,6 +239,7 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let (not, expected) = if inverted { @@ -232,8 +247,8 @@ where } else { ("", "an infinite number") }; - let marked_actual = mark_unexpected(actual, format); - let marked_expected = mark_missing_string(expected, format); + let marked_actual = mark_unexpected(actual, representation, format); + let marked_expected = mark_missing(expected, &DisplayRepresentation, format); format!( "expected {expression} to be {not}infinite\n but was: {marked_actual}\n expected: {marked_expected}" ) @@ -242,9 +257,10 @@ where impl Invertible for IsInfinite {} -impl AssertNotANumber for Spec<'_, S, R> +impl AssertNotANumber for Spec<'_, S, D, R> where - S: IsNanProperty + Debug, + S: IsNanProperty, + D: Represent, R: FailingStrategy, { fn is_not_a_number(self) -> Self { @@ -256,9 +272,10 @@ where } } -impl Expectation for IsANumber +impl Expectation for IsANumber where - S: IsNanProperty + Debug, + S: IsNanProperty, + D: Represent, { fn test(&mut self, subject: &S) -> bool { !subject.is_nan_property() @@ -269,6 +286,7 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let (not, expected) = if inverted { @@ -276,8 +294,8 @@ where } else { ("", "a number") }; - let marked_actual = mark_unexpected(actual, format); - let marked_expected = mark_missing_string(expected, format); + let marked_actual = mark_unexpected(actual, representation, format); + let marked_expected = mark_missing(expected, &DisplayRepresentation, format); format!( "expected {expression} to be {not}a number\n but was: {marked_actual}\n expected: {marked_expected}" ) @@ -286,9 +304,10 @@ where impl Invertible for IsANumber {} -impl AssertDecimalNumber for Spec<'_, S, R> +impl AssertDecimalNumber for Spec<'_, S, D, R> where - S: DecimalProperties + Debug, + S: DecimalProperties, + D: Represent + Represent + Represent, R: FailingStrategy, { fn has_scale_of(self, expected_scale: i64) -> Self { @@ -304,9 +323,10 @@ where } } -impl Expectation for HasScaleOf +impl Expectation for HasScaleOf where - S: DecimalProperties + Debug, + S: DecimalProperties, + D: Represent, { fn test(&mut self, subject: &S) -> bool { subject.scale_property() == self.expected_scale @@ -317,23 +337,26 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let not = if inverted { "not " } else { "" }; let expected_scale = self.expected_scale; - let marked_actual = mark_unexpected(&actual.scale_property(), format); - let marked_expected = mark_missing(&expected_scale, format); + let marked_actual = mark_unexpected(&actual.scale_property(), representation, format); + let marked_expected = mark_missing(&expected_scale, representation, format); + let represented_expected_scale = Represented::from((&expected_scale, representation)); format!( - "expected {expression} to {not}have a scale of {expected_scale}\n but was: {marked_actual}\n expected: {not}{marked_expected}" + "expected {expression} to {not}have a scale of {represented_expected_scale}\n but was: {marked_actual}\n expected: {not}{marked_expected}" ) } } impl Invertible for HasScaleOf {} -impl Expectation for HasPrecisionOf +impl Expectation for HasPrecisionOf where - S: DecimalProperties + Debug, + S: DecimalProperties, + D: Represent, { fn test(&mut self, subject: &S) -> bool { subject.precision_property() == self.expected_precision @@ -344,21 +367,25 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let not = if inverted { "not " } else { "" }; let expected_precision = self.expected_precision; - let marked_actual = mark_unexpected(&actual.precision_property(), format); - let marked_expected = mark_missing(&expected_precision, format); + let marked_actual = mark_unexpected(&actual.precision_property(), representation, format); + let marked_expected = mark_missing(&expected_precision, representation, format); + let represented_expected_precision = + Represented::from((&expected_precision, representation)); format!( - "expected {expression} to {not}have a precision of {expected_precision}\n but was: {marked_actual}\n expected: {not}{marked_expected}" + "expected {expression} to {not}have a precision of {represented_expected_precision}\n but was: {marked_actual}\n expected: {not}{marked_expected}" ) } } -impl Expectation for IsInteger +impl Expectation for IsInteger where - S: DecimalProperties + Debug, + S: DecimalProperties, + D: Represent, { fn test(&mut self, subject: &S) -> bool { subject.is_integer_property() @@ -369,6 +396,7 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let (not, expected) = if inverted { @@ -376,8 +404,8 @@ where } else { ("", "an integer value") }; - let marked_actual = mark_unexpected(&actual, format); - let marked_expected = mark_missing_string(expected, format); + let marked_actual = mark_unexpected(actual, representation, format); + let marked_expected = mark_missing(expected, &DisplayRepresentation, format); format!( "expected {expression} to be {not}an integer value\n but was: {marked_actual}\n expected: {marked_expected}" ) diff --git a/src/option/mod.rs b/src/option/mod.rs index 5f66877..096aa20 100644 --- a/src/option/mod.rs +++ b/src/option/mod.rs @@ -4,14 +4,14 @@ use crate::assertions::{AssertHasValue, AssertOption, AssertOptionValue}; use crate::colored::{mark_missing, mark_unexpected}; use crate::expectations::{HasValue, IsNone, IsSome, has_value, is_none, is_some}; use crate::spec::{ - DiffFormat, Expectation, Expecting, Expression, FailingStrategy, Invertible, Spec, Unknown, + DiffFormat, DisplayRepresentation, Expectation, Expecting, Expression, FailingStrategy, + Invertible, Represent, Represented, RepresentedBy, Spec, }; -use crate::std::fmt::Debug; use crate::std::{format, string::String}; -impl AssertOption for Spec<'_, Option, R> +impl AssertOption for Spec<'_, Option, D, R> where - S: Debug, + D: Represent, R: FailingStrategy, { fn is_some(self) -> Self { @@ -23,9 +23,9 @@ where } } -impl AssertOption for Spec<'_, &Option, R> +impl AssertOption for Spec<'_, &Option, D, R> where - S: Debug, + D: Represent, R: FailingStrategy, { fn is_some(self) -> Self { @@ -37,42 +37,48 @@ where } } -impl<'a, T, R> AssertOptionValue for Spec<'a, Option, R> +impl<'a, T, D, R> AssertOptionValue for Spec<'a, Option, D, R> where + D: Represent + Clone, R: FailingStrategy, { - type Some = Spec<'a, T, R>; + type Some = Spec<'a, T, D, R>; fn some(self) -> Self::Some { + let value_representation = self.representation().clone(); self.mapping(|subject| match subject { None => { panic!("expected the subject to be `Some(_)`, but was `None`") }, Some(value) => value, }) + .represented_by(value_representation) } } -impl<'a, T, R> AssertOptionValue for Spec<'a, &'a Option, R> +impl<'a, T, D, R> AssertOptionValue for Spec<'a, &'a Option, D, R> where + D: Represent + Clone, R: FailingStrategy, { - type Some = Spec<'a, &'a T, R>; + type Some = Spec<'a, &'a T, D, R>; fn some(self) -> Self::Some { + let value_representation = self.representation().clone(); self.mapping(|subject| match subject { None => { panic!("expected the subject to be `Some(_)`, but was `None`") }, Some(value) => value, }) + .represented_by(value_representation) } } -impl AssertHasValue for Spec<'_, Option, R> +impl AssertHasValue for Spec<'_, Option, D, R> where - S: PartialEq + Debug, - E: Debug, + T: PartialEq, + D: Represent + Represent, R: FailingStrategy, { fn has_value(self, expected: E) -> Self { @@ -80,10 +86,10 @@ where } } -impl AssertHasValue for Spec<'_, &Option, R> +impl AssertHasValue for Spec<'_, &Option, D, R> where - S: PartialEq + Debug, - E: Debug, + T: PartialEq, + D: Represent + Represent, R: FailingStrategy, { fn has_value(self, expected: E) -> Self { @@ -91,9 +97,9 @@ where } } -impl Expectation> for IsSome +impl Expectation, D> for IsSome where - T: Debug, + D: Represent, { fn test(&mut self, subject: &Option) -> bool { subject.is_some() @@ -104,23 +110,30 @@ where expression: &Expression<'_>, actual: &Option, _inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { - let expected = Some(Unknown); - let marked_actual = mark_unexpected(actual, format); - let marked_expected = mark_missing(&expected, format); + let marked_actual = match actual { + Some(value) => mark_unexpected( + &format!("Some({})", Represented::from((value, representation))), + &DisplayRepresentation, + format, + ), + None => mark_unexpected("None", &DisplayRepresentation, format), + }; + let marked_expected = mark_missing("Some(_)", &DisplayRepresentation, format); format!( - "expected {expression} to be {expected:?}\n but was: {marked_actual}\n expected: {marked_expected}" + "expected {expression} to be Some(_)\n but was: {marked_actual}\n expected: {marked_expected}" ) } } -impl Expectation<&Option> for IsSome +impl Expectation<&Option, D> for IsSome where - T: Debug, + D: Represent, { fn test(&mut self, subject: &&Option) -> bool { - >>::test(self, subject) + , D>>::test(self, subject) } fn message( @@ -128,15 +141,23 @@ where expression: &Expression<'_>, actual: &&Option, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { - >>::message(self, expression, actual, inverted, format) + , D>>::message( + self, + expression, + actual, + inverted, + representation, + format, + ) } } -impl Expectation> for IsNone +impl Expectation, D> for IsNone where - T: Debug, + D: Represent, { fn test(&mut self, subject: &Option) -> bool { subject.is_none() @@ -147,23 +168,30 @@ where expression: &Expression<'_>, actual: &Option, _inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { - let expected = None::; - let marked_actual = mark_unexpected(actual, format); - let marked_expected = mark_missing(&expected, format); + let marked_actual = match actual { + Some(value) => mark_unexpected( + &format!("Some({:?})", Represented::from((value, representation))), + &DisplayRepresentation, + format, + ), + None => mark_unexpected("None", &DisplayRepresentation, format), + }; + let marked_expected = mark_missing("None", &DisplayRepresentation, format); format!( - "expected {expression} to be {expected:?}\n but was: {marked_actual}\n expected: {marked_expected}" + "expected {expression} to be None\n but was: {marked_actual}\n expected: {marked_expected}" ) } } -impl Expectation<&Option> for IsNone +impl Expectation<&Option, D> for IsNone where - T: Debug, + D: Represent, { fn test(&mut self, subject: &&Option) -> bool { - >>::test(self, subject) + , D>>::test(self, subject) } fn message( @@ -171,16 +199,24 @@ where expression: &Expression<'_>, actual: &&Option, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { - >>::message(self, expression, actual, inverted, format) + , D>>::message( + self, + expression, + actual, + inverted, + representation, + format, + ) } } -impl Expectation> for HasValue +impl Expectation, D> for HasValue where - T: PartialEq + Debug, - E: Debug, + T: PartialEq, + D: Represent + Represent, { fn test(&mut self, subject: &Option) -> bool { subject @@ -193,27 +229,40 @@ where expression: &Expression<'_>, actual: &Option, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let not = if inverted { "not " } else { "" }; let expected = &self.expected; - let marked_actual = mark_unexpected(actual, format); - let marked_expected = mark_missing(&Some(expected), format); + let marked_actual = match actual { + Some(value) => mark_unexpected( + &format!("Some({:?})", Represented::from((value, representation))), + &DisplayRepresentation, + format, + ), + None => mark_unexpected("None", &DisplayRepresentation, format), + }; + let marked_expected = mark_missing( + &format!("Some({:?})", Represented::from((expected, representation))), + &DisplayRepresentation, + format, + ); + let represented_expected = Represented::from((expected, representation)); format!( - "expected {expression} to be some {not}containing {expected:?}\n but was: {marked_actual}\n expected: {not}{marked_expected}" + "expected {expression} to be some {not}containing {represented_expected:?}\n but was: {marked_actual}\n expected: {not}{marked_expected}" ) } } impl Invertible for HasValue {} -impl Expectation<&Option> for HasValue +impl Expectation<&Option, D> for HasValue where - T: PartialEq + Debug, - E: Debug, + T: PartialEq, + D: Represent + Represent, { fn test(&mut self, subject: &&Option) -> bool { - >>::test(self, subject) + , D>>::test(self, subject) } fn message( @@ -221,9 +270,17 @@ where expression: &Expression<'_>, actual: &&Option, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { - >>::message(self, expression, actual, inverted, format) + , D>>::message( + self, + expression, + actual, + inverted, + representation, + format, + ) } } diff --git a/src/order/mod.rs b/src/order/mod.rs index 72cbf89..792cf7f 100644 --- a/src/order/mod.rs +++ b/src/order/mod.rs @@ -7,15 +7,15 @@ use crate::expectations::{ is_at_least, is_at_most, is_before, is_between, is_greater_than, is_less_than, }; use crate::spec::{ - DiffFormat, Expectation, Expecting, Expression, FailingStrategy, Invertible, Spec, + DiffFormat, Expectation, Expecting, Expression, FailingStrategy, Invertible, Represent, + Represented, Spec, }; -use crate::std::fmt::Debug; use crate::std::{format, string::String}; -impl AssertOrder for Spec<'_, S, R> +impl AssertOrder for Spec<'_, S, D, R> where - S: PartialOrd + Debug, - E: Debug, + S: PartialOrd, + D: Represent + Represent, R: FailingStrategy, { fn is_less_than(self, expected: E) -> Self { @@ -47,10 +47,10 @@ where } } -impl Expectation for IsLessThan +impl Expectation for IsLessThan where - S: PartialOrd + Debug, - E: Debug, + S: PartialOrd, + D: Represent + Represent, { fn test(&mut self, subject: &S) -> bool { subject < &self.expected @@ -61,24 +61,25 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let (not, cmp) = if inverted { ("not ", ">=") } else { ("", "<") }; - let marked_actual = mark_unexpected(actual, format); - let marked_expected = mark_missing(&self.expected, format); + let marked_actual = mark_unexpected(actual, representation, format); + let marked_expected = mark_missing(&self.expected, representation, format); + let represented_expected = Represented::from((&self.expected, representation)); format!( - "expected {expression} to be {not}less than {:?}\n but was: {marked_actual}\n expected: {cmp} {marked_expected}", - self.expected, + "expected {expression} to be {not}less than {represented_expected:?}\n but was: {marked_actual}\n expected: {cmp} {marked_expected}", ) } } impl Invertible for IsLessThan {} -impl Expectation for IsAtMost +impl Expectation for IsAtMost where - S: PartialOrd + Debug, - E: Debug, + S: PartialOrd, + D: Represent + Represent, { fn test(&mut self, subject: &S) -> bool { subject <= &self.expected @@ -89,24 +90,25 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let (not, cmp) = if inverted { ("not ", ">") } else { ("", "<=") }; - let marked_actual = mark_unexpected(actual, format); - let marked_expected = mark_missing(&self.expected, format); + let marked_actual = mark_unexpected(actual, representation, format); + let marked_expected = mark_missing(&self.expected, representation, format); + let represented_expected = Represented::from((&self.expected, representation)); format!( - "expected {expression} to be {not}at most {:?}\n but was: {marked_actual}\n expected: {cmp} {marked_expected}", - self.expected, + "expected {expression} to be {not}at most {represented_expected:?}\n but was: {marked_actual}\n expected: {cmp} {marked_expected}", ) } } impl Invertible for IsAtMost {} -impl Expectation for IsGreaterThan +impl Expectation for IsGreaterThan where - S: PartialOrd + Debug, - E: Debug, + S: PartialOrd, + D: Represent + Represent, { fn test(&mut self, subject: &S) -> bool { subject > &self.expected @@ -117,24 +119,25 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let (not, cmp) = if inverted { ("not ", "<=") } else { ("", ">") }; - let marked_actual = mark_unexpected(actual, format); - let marked_expected = mark_missing(&self.expected, format); + let marked_actual = mark_unexpected(actual, representation, format); + let marked_expected = mark_missing(&self.expected, representation, format); + let represented_expected = Represented::from((&self.expected, representation)); format!( - "expected {expression} to be {not}greater than {:?}\n but was: {marked_actual}\n expected: {cmp} {marked_expected}", - self.expected, + "expected {expression} to be {not}greater than {represented_expected:?}\n but was: {marked_actual}\n expected: {cmp} {marked_expected}", ) } } impl Invertible for IsGreaterThan {} -impl Expectation for IsAtLeast +impl Expectation for IsAtLeast where - S: PartialOrd + Debug, - E: Debug, + S: PartialOrd, + D: Represent + Represent, { fn test(&mut self, subject: &S) -> bool { subject >= &self.expected @@ -145,24 +148,25 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let (not, cmp) = if inverted { ("not ", "<") } else { ("", ">=") }; - let marked_actual = mark_unexpected(actual, format); - let marked_expected = mark_missing(&self.expected, format); + let marked_actual = mark_unexpected(actual, representation, format); + let marked_expected = mark_missing(&self.expected, representation, format); + let represented_expected = Represented::from((&self.expected, representation)); format!( - "expected {expression} to be {not}at least {:?}\n but was: {marked_actual}\n expected: {cmp} {marked_expected}", - self.expected, + "expected {expression} to be {not}at least {represented_expected:?}\n but was: {marked_actual}\n expected: {cmp} {marked_expected}", ) } } impl Invertible for IsAtLeast {} -impl Expectation for IsBefore +impl Expectation for IsBefore where - S: PartialOrd + Debug, - E: Debug, + S: PartialOrd, + D: Represent + Represent, { fn test(&mut self, subject: &S) -> bool { subject < &self.expected @@ -173,24 +177,25 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let (not, cmp) = if inverted { ("not ", ">=") } else { ("", "<") }; - let marked_actual = mark_unexpected(actual, format); - let marked_expected = mark_missing(&self.expected, format); + let marked_actual = mark_unexpected(actual, representation, format); + let marked_expected = mark_missing(&self.expected, representation, format); + let represented_expected = Represented::from((&self.expected, representation)); format!( - "expected {expression} to be {not}before {:?}\n but was: {marked_actual}\n expected: {cmp} {marked_expected}", - self.expected, + "expected {expression} to be {not}before {represented_expected:?}\n but was: {marked_actual}\n expected: {cmp} {marked_expected}", ) } } impl Invertible for IsBefore {} -impl Expectation for IsAfter +impl Expectation for IsAfter where - S: PartialOrd + Debug, - E: Debug, + S: PartialOrd, + D: Represent + Represent, { fn test(&mut self, subject: &S) -> bool { subject > &self.expected @@ -201,24 +206,25 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let (not, cmp) = if inverted { ("not ", "<=") } else { ("", ">") }; - let marked_actual = mark_unexpected(actual, format); - let marked_expected = mark_missing(&self.expected, format); + let marked_actual = mark_unexpected(actual, representation, format); + let marked_expected = mark_missing(&self.expected, representation, format); + let represented_expected = Represented::from((&self.expected, representation)); format!( - "expected {expression} to be {not}after {:?}\n but was: {marked_actual}\n expected: {cmp} {marked_expected}", - self.expected, + "expected {expression} to be {not}after {represented_expected:?}\n but was: {marked_actual}\n expected: {cmp} {marked_expected}", ) } } impl Invertible for IsAfter {} -impl Expectation for IsBetween +impl Expectation for IsBetween where - S: PartialOrd + Debug, - E: Debug, + S: PartialOrd, + D: Represent + Represent, { fn test(&mut self, subject: &S) -> bool { subject >= &self.min && subject <= &self.max @@ -229,6 +235,7 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let (not, cmp) = if inverted { @@ -236,20 +243,21 @@ where } else { ("", "<= x <=") }; - let marked_actual = mark_unexpected(actual, format); + let expected_min = Represented::from((&self.min, representation)); + let expected_max = Represented::from((&self.max, representation)); + let marked_actual = mark_unexpected(actual, representation, format); let marked_start = if (actual < &self.min) || inverted { - mark_missing(&self.min, format) + mark_missing(&self.min, representation, format) } else { - format!("{:?}", self.min) + format!("{expected_min:?}") }; let marked_end = if (actual > &self.max) || inverted { - mark_missing(&self.max, format) + mark_missing(&self.max, representation, format) } else { - format!("{:?}", self.max) + format!("{expected_max:?}") }; format!( - "expected {expression} to be {not}between {:?} and {:?}\n but was: {marked_actual}\n expected: {marked_start} {cmp} {marked_end}", - self.min, self.max + "expected {expression} to be {not}between {expected_min:?} and {expected_max:?}\n but was: {marked_actual}\n expected: {marked_start} {cmp} {marked_end}", ) } } diff --git a/src/panic/mod.rs b/src/panic/mod.rs index 3c908c6..a09bfe2 100644 --- a/src/panic/mod.rs +++ b/src/panic/mod.rs @@ -1,21 +1,24 @@ //! Implementation of assertions for code that should or should not panic. use crate::assertions::AssertCodePanics; -use crate::colored::{mark_missing_string, mark_unexpected_string}; +use crate::colored::{mark_missing, mark_unexpected}; use crate::expectations::{DoesNotPanic, DoesPanic, does_not_panic, does_panic}; -use crate::spec::{Code, DiffFormat, Expectation, Expecting, Expression, FailingStrategy, Spec}; +use crate::spec::{ + Code, DebugRepresentation, DiffFormat, DisplayRepresentation, Expectation, Expecting, + Expression, FailingStrategy, Spec, +}; use crate::std::any::Any; use crate::std::panic; const ONLY_ONE_EXPECTATION: &str = "only one expectation allowed when asserting closures!"; const UNKNOWN_PANIC_MESSAGE: &str = ""; -impl<'a, S, R> AssertCodePanics for Spec<'a, Code, R> +impl<'a, S, D, R> AssertCodePanics for Spec<'a, Code, D, R> where S: FnOnce(), R: FailingStrategy, { - type Mapped = Spec<'a, (), R>; + type Mapped = Spec<'a, (), DebugRepresentation, R>; fn does_not_panic(self) -> Self::Mapped { self.expecting(does_not_panic()).mapping(|_| ()) @@ -31,7 +34,7 @@ where } } -impl Expectation> for DoesNotPanic +impl Expectation, D> for DoesNotPanic where S: FnOnce(), { @@ -56,6 +59,7 @@ where expression: &Expression<'_>, _actual: &Code, _inverted: bool, + _representation: &D, format: &DiffFormat, ) -> String { let panic_message = read_panic_message(self.actual_message.as_ref()) @@ -64,8 +68,9 @@ where if panic_message == ONLY_ONE_EXPECTATION { format!("error in test assertion: {ONLY_ONE_EXPECTATION}") } else { - let marked_did_panic = mark_unexpected_string("did panic", format); - let marked_panic_message = mark_unexpected_string(&panic_message, format); + let marked_did_panic = mark_unexpected("did panic", &DisplayRepresentation, format); + let marked_panic_message = + mark_unexpected(&panic_message, &DisplayRepresentation, format); format!( "expected {expression} to not panic, but {marked_did_panic}\n with message: \"{marked_panic_message}\"" ) @@ -73,7 +78,7 @@ where } } -impl Expectation> for DoesPanic +impl Expectation, D> for DoesPanic where S: FnOnce(), { @@ -106,14 +111,17 @@ where expression: &Expression<'_>, _actual: &Code, _inverted: bool, + _representation: &D, format: &DiffFormat, ) -> String { if let Some(actual_message) = self.actual_message.as_ref() { if actual_message == ONLY_ONE_EXPECTATION { format!("error in test assertion: {ONLY_ONE_EXPECTATION}") } else if let Some(expected_message) = &self.expected_message { - let marked_expected_message = mark_missing_string(expected_message, format); - let marked_actual_message = mark_unexpected_string(actual_message, format); + let marked_expected_message = + mark_missing(expected_message, &DisplayRepresentation, format); + let marked_actual_message = + mark_unexpected(actual_message, &DisplayRepresentation, format); format!( "expected {expression} to panic with message {expected_message:?}\n but was: \"{marked_actual_message}\"\n expected: \"{marked_expected_message}\"" ) @@ -122,12 +130,14 @@ where format!("expected {expression} to panic, but did not panic") } } else if let Some(expected_message) = &self.expected_message { - let marked_did_not_panic = mark_unexpected_string("did not panic", format); + let marked_did_not_panic = + mark_unexpected("did not panic", &DisplayRepresentation, format); format!( "expected {expression} to panic with message {expected_message:?},\n but {marked_did_not_panic}" ) } else { - let marked_did_not_panic = mark_unexpected_string("did not panic", format); + let marked_did_not_panic = + mark_unexpected("did not panic", &DisplayRepresentation, format); format!("expected {expression} to panic, but {marked_did_not_panic}") } } diff --git a/src/predicate/mod.rs b/src/predicate/mod.rs index e4aee09..741b19d 100644 --- a/src/predicate/mod.rs +++ b/src/predicate/mod.rs @@ -1,12 +1,13 @@ //! Implementation of the predicate assertion. use crate::expectations::Predicate; -use crate::spec::{DiffFormat, Expectation, Expression, Invertible}; +use crate::spec::{DiffFormat, Expectation, Expression, Invertible, Represent, Represented}; use crate::std::{format, string::String}; -impl Expectation for Predicate

+impl Expectation for Predicate

where P: Fn(&S) -> bool, + D: Represent, { fn test(&mut self, subject: &S) -> bool { (self.predicate)(subject) @@ -15,12 +16,14 @@ where fn message( &self, expression: &Expression<'_>, - _actual: &S, + actual: &S, inverted: bool, + representation: &D, _format: &DiffFormat, ) -> String { + let represented_actual = Represented::from((actual, representation)); self.message.clone().unwrap_or_else(|| { - format!("expected {expression} to satisfy the given predicate, but returned {inverted}") + format!("expected {expression} to satisfy the given predicate, but returned {inverted}\n actual: {represented_actual}") }) } } diff --git a/src/predicate/tests.rs b/src/predicate/tests.rs index 6ee8c15..893cd53 100644 --- a/src/predicate/tests.rs +++ b/src/predicate/tests.rs @@ -20,7 +20,7 @@ fn verify_that_subject_satisfies_predicate_fails() { assert_eq!( failures, - &["expected my_thing to satisfy the given predicate, but returned false\n"] + &["expected my_thing to satisfy the given predicate, but returned false\n actual: 51\n"] ); } diff --git a/src/prelude.rs b/src/prelude.rs index de8d260..c9590b1 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -20,8 +20,8 @@ pub use super::{ colored::{DEFAULT_DIFF_FORMAT, DIFF_FORMAT_NO_HIGHLIGHT}, properties::*, spec::{ - And, CollectFailures, DoFail, Expecting, GetFailures, Location, PanicOnFail, Satisfies, - SoftPanic, assert_that, verify_that, + And, CollectFailures, DoFail, Expecting, GetFailures, Location, PanicOnFail, Represent, + RepresentedAs, RepresentedBy, Satisfies, SoftPanic, assert_that, verify_that, }, verify_that, }; diff --git a/src/range/mod.rs b/src/range/mod.rs index d564001..7f3c806 100644 --- a/src/range/mod.rs +++ b/src/range/mod.rs @@ -1,13 +1,13 @@ //! Implementation of assertions for `Range` and `RangeInclusive` values. use crate::assertions::AssertInRange; -use crate::colored::{mark_missing, mark_missing_string, mark_unexpected}; +use crate::colored::{mark_missing, mark_unexpected}; use crate::expectations::{IsInRange, is_in_range, not}; use crate::properties::IsEmptyProperty; use crate::spec::{ - DiffFormat, Expectation, Expecting, Expression, FailingStrategy, Invertible, Spec, + DiffFormat, DisplayRepresentation, Expectation, Expecting, Expression, FailingStrategy, + Invertible, Represent, Represented, Spec, }; -use crate::std::fmt::Debug; use crate::std::format; use crate::std::ops::{Bound, Range, RangeBounds, RangeInclusive}; use crate::std::string::String; @@ -30,32 +30,36 @@ where } } -impl AssertInRange for Spec<'_, S, R> +impl AssertInRange for Spec<'_, S, D, R> where - S: PartialOrd + Debug, - E: PartialOrd + Debug, + S: PartialOrd, + E: PartialOrd, + D: Represent + Represent, R: FailingStrategy, { fn is_in_range(self, range: U) -> Self where - U: RangeBounds + Debug, + U: RangeBounds, + D: Represent, { self.expecting(is_in_range(range)) } fn is_not_in_range(self, range: U) -> Self where - U: RangeBounds + Debug, + U: RangeBounds, + D: Represent, { self.expecting(not(is_in_range(range))) } } -impl Expectation for IsInRange +impl Expectation for IsInRange where - S: PartialOrd + Debug, - E: PartialOrd + Debug, - R: RangeBounds + Debug, + S: PartialOrd, + E: PartialOrd, + R: RangeBounds, + D: Represent + Represent + Represent, { fn test(&mut self, subject: &S) -> bool { self.expected_range.contains(subject) @@ -66,19 +70,28 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { - let marked_actual = mark_unexpected(actual, format); + let marked_actual = mark_unexpected(actual, representation, format); let (not, marked_expected) = if inverted { let marked_expected_start = match self.expected_range.start_bound() { - Bound::Included(start) => format!("< {}", mark_missing(start, format)), - Bound::Excluded(start) => format!("<= {}", mark_missing(start, format)), - Bound::Unbounded => format!("< {}", mark_missing_string("..", format)), + Bound::Included(start) => { + format!("< {}", mark_missing(start, representation, format)) + }, + Bound::Excluded(start) => { + format!("<= {}", mark_missing(start, representation, format)) + }, + Bound::Unbounded => { + format!("< {}", mark_missing("..", &DisplayRepresentation, format)) + }, }; let marked_expected_end = match self.expected_range.end_bound() { - Bound::Included(end) => format!("> {}", mark_missing(end, format)), - Bound::Excluded(end) => format!(">= {}", mark_missing(end, format)), - Bound::Unbounded => format!("> {}", mark_missing_string("..", format)), + Bound::Included(end) => format!("> {}", mark_missing(end, representation, format)), + Bound::Excluded(end) => format!(">= {}", mark_missing(end, representation, format)), + Bound::Unbounded => { + format!("> {}", mark_missing("..", &DisplayRepresentation, format)) + }, }; ( @@ -89,36 +102,44 @@ where let marked_expected_start = match self.expected_range.start_bound() { Bound::Included(start) => { if actual < start { - format!("{} <=", mark_missing(start, format)) + format!("{} <=", mark_missing(start, representation, format)) } else { - format!("{start:?} <=") + let represented_start = Represented::from((start, representation)); + format!("{represented_start:?} <=") } }, Bound::Excluded(start) => { if actual <= start { - format!("{} <", mark_missing(start, format)) + format!("{} <", mark_missing(start, representation, format)) } else { - format!("{start:?} <") + let represented_start = Represented::from((start, representation)); + format!("{represented_start:?} <") } }, - Bound::Unbounded => format!("{} <", mark_missing_string("..", format)), + Bound::Unbounded => { + format!("{} <", mark_missing("..", &DisplayRepresentation, format)) + }, }; let marked_expected_end = match self.expected_range.end_bound() { Bound::Included(end) => { if actual > end { - format!("<= {}", mark_missing(end, format)) + format!("<= {}", mark_missing(end, representation, format)) } else { - format!("<= {end:?}") + let represented_end = Represented::from((end, representation)); + format!("<= {represented_end:?}") } }, Bound::Excluded(end) => { if actual >= end { - format!("< {}", mark_missing(end, format)) + format!("< {}", mark_missing(end, representation, format)) } else { - format!("< {end:?}") + let represented_end = Represented::from((end, representation)); + format!("< {represented_end:?}") } }, - Bound::Unbounded => format!("< {}", mark_missing_string("..", format)), + Bound::Unbounded => { + format!("< {}", mark_missing("..", &DisplayRepresentation, format)) + }, }; ( @@ -127,9 +148,9 @@ where ) }; + let represented_range = Represented::from((&self.expected_range, representation)); format!( - "expected {expression} to be {not}within range of {:?}\n but was: {marked_actual}\n expected: {marked_expected}", - self.expected_range, + "expected {expression} to be {not}within range of {represented_range:?}\n but was: {marked_actual}\n expected: {marked_expected}", ) } } diff --git a/src/recursive_comparison/mod.rs b/src/recursive_comparison/mod.rs index 0c21aee..ae35728 100644 --- a/src/recursive_comparison/mod.rs +++ b/src/recursive_comparison/mod.rs @@ -338,14 +338,14 @@ use serde_core::Serialize; /// /// See the [module documentation](crate::recursive_comparison) for details /// about field-by-field recursive comparison. -pub struct RecursiveComparison<'a, S, R> { - spec: Spec<'a, S, R>, +pub struct RecursiveComparison<'a, S, D, R> { + spec: Spec<'a, S, D, R>, compared_fields: Vec>, ignored_fields: Vec>, ignore_not_expected_fields: bool, } -impl GetFailures for RecursiveComparison<'_, S, R> { +impl GetFailures for RecursiveComparison<'_, S, D, R> { fn has_failures(&self) -> bool { self.spec.has_failures() } @@ -359,7 +359,7 @@ impl GetFailures for RecursiveComparison<'_, S, R> { } } -impl DoFail for RecursiveComparison<'_, S, R> +impl DoFail for RecursiveComparison<'_, S, D, R> where R: FailingStrategy, { @@ -372,14 +372,14 @@ where } } -impl SoftPanic for RecursiveComparison<'_, S, CollectFailures> { +impl SoftPanic for RecursiveComparison<'_, S, D, CollectFailures> { fn soft_panic(&self) { self.spec.soft_panic(); } } -impl<'a, S, R> RecursiveComparison<'a, S, R> { - pub(crate) fn new(spec: Spec<'a, S, R>) -> Self { +impl<'a, S, D, R> RecursiveComparison<'a, S, D, R> { + pub(crate) fn new(spec: Spec<'a, S, D, R>) -> Self { Self { spec, compared_fields: vec![], @@ -623,7 +623,7 @@ fn display_compare_details(compared: &ComparisonResult<'_>, diff_format: &DiffFo display_details } -impl AssertEquality for RecursiveComparison<'_, S, R> +impl AssertEquality for RecursiveComparison<'_, S, D, R> where S: Serialize, E: Serialize, @@ -674,7 +674,7 @@ where } } -impl AssertEquivalence for RecursiveComparison<'_, S, R> +impl AssertEquivalence for RecursiveComparison<'_, S, D, R> where S: Serialize, R: FailingStrategy, diff --git a/src/representation/mod.rs b/src/representation/mod.rs new file mode 100644 index 0000000..87c2771 --- /dev/null +++ b/src/representation/mod.rs @@ -0,0 +1,2 @@ +#[cfg(test)] +mod tests; diff --git a/src/representation/tests.rs b/src/representation/tests.rs new file mode 100644 index 0000000..bea064b --- /dev/null +++ b/src/representation/tests.rs @@ -0,0 +1,56 @@ +use crate::prelude::*; +use crate::std::fmt; + +#[derive(PartialEq)] +struct Foo(i32); + +#[derive(PartialEq)] +struct Bar(i32); + +impl PartialEq for Foo { + fn eq(&self, other: &Bar) -> bool { + self.0 == other.0 + } +} + +#[derive(Clone, Copy)] +pub struct FooBarRepresentation; + +impl Represent for FooBarRepresentation { + fn represent(&self, value: &Foo, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", value.0) + } +} + +impl Represent for FooBarRepresentation { + fn represent(&self, value: &Bar, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", value.0) + } +} + +#[test] +fn represented_as_non_debug_is_equal_to_same_non_debug() { + let foo = Foo(-1); + + assert_that(foo) + .represented_as(|v, f| write!(f, "{}", v.0)) + .is_equal_to(Foo(-1)); +} + +#[test] +fn represented_by_non_debug_is_equal_to_same_non_debug() { + let foo = Foo(-1); + + assert_that(foo) + .represented_by(FooBarRepresentation) + .is_equal_to(Foo(-1)); +} + +#[test] +fn represented_by_non_debug_is_equal_to_some_other_non_debug() { + let foo = Foo(-1); + + assert_that(foo) + .represented_by(FooBarRepresentation) + .is_equal_to(Bar(-1)); +} diff --git a/src/result/mod.rs b/src/result/mod.rs index 057a060..7a9436a 100644 --- a/src/result/mod.rs +++ b/src/result/mod.rs @@ -8,7 +8,8 @@ use crate::expectations::{ HasError, HasValue, IsErr, IsOk, has_error, has_value, is_equal_to, is_err, is_ok, }; use crate::spec::{ - DiffFormat, Expectation, Expecting, Expression, FailingStrategy, Invertible, Spec, Unknown, + DebugRepresentation, DiffFormat, DisplayRepresentation, Expectation, Expecting, Expression, + FailingStrategy, Invertible, Represent, Represented, RepresentedBy, Spec, }; use crate::std::fmt::{Debug, Display}; use crate::std::{ @@ -16,10 +17,9 @@ use crate::std::{ string::{String, ToString}, }; -impl AssertResult for Spec<'_, Result, R> +impl AssertResult for Spec<'_, Result, D, R> where - T: Debug, - E: Debug, + D: Represent + Represent, R: FailingStrategy, { fn is_ok(self) -> Self { @@ -31,10 +31,9 @@ where } } -impl AssertResult for Spec<'_, &Result, R> +impl AssertResult for Spec<'_, &Result, D, R> where - T: Debug, - E: Debug, + D: Represent + Represent, R: FailingStrategy, { fn is_ok(self) -> Self { @@ -46,65 +45,74 @@ where } } -impl<'a, T, E, R> AssertResultValue for Spec<'a, Result, R> +impl<'a, T, E, D, R> AssertResultValue for Spec<'a, Result, D, R> where - T: Debug, - E: Debug, + D: Represent + Represent + Clone, { - type Ok = Spec<'a, T, R>; - type Err = Spec<'a, E, R>; + type Ok = Spec<'a, T, D, R>; + type Err = Spec<'a, E, D, R>; fn ok(self) -> Self::Ok { + let representation = self.representation().clone(); self.mapping(|subject| match subject { Ok(value) => value, Err(error) => { + let error = Represented::from((&error, &representation)); panic!("expected the subject to be `Ok(_)`, but was `Err({error:?})`") }, }) + .represented_by(representation) } fn err(self) -> Self::Err { + let representation = self.representation().clone(); self.mapping(|subject| match subject { Ok(value) => { + let value = Represented::from((&value, &representation)); panic!("expected the subject to be `Err(_)`, but was `Ok({value:?})`") }, Err(error) => error, }) + .represented_by(representation) } } -impl<'a, T, E, R> AssertResultValue for Spec<'a, &'a Result, R> +impl<'a, T, E, D, R> AssertResultValue for Spec<'a, &'a Result, D, R> where - T: Debug, - E: Debug, + D: Represent + Represent + Clone, { - type Ok = Spec<'a, &'a T, R>; - type Err = Spec<'a, &'a E, R>; + type Ok = Spec<'a, &'a T, D, R>; + type Err = Spec<'a, &'a E, D, R>; fn ok(self) -> Self::Ok { + let representation = self.representation().clone(); self.mapping(|subject| match subject { Ok(value) => value, Err(error) => { + let error = Represented::from((error, &representation)); panic!("expected the subject to be `Ok(_)`, but was `Err({error:?})`") }, }) + .represented_by(representation) } fn err(self) -> Self::Err { + let representation = self.representation().clone(); self.mapping(|subject| match subject { Ok(value) => { + let value = Represented::from((value, &representation)); panic!("expected the subject to be `Err(_)`, but was `Ok({value:?})`") }, Err(error) => error, }) + .represented_by(representation) } } -impl AssertHasValue for Spec<'_, Result, R> +impl AssertHasValue for Spec<'_, Result, D, R> where - T: PartialEq + Debug, - E: Debug, - X: Debug, + T: PartialEq, + D: Represent + Represent + Represent, R: FailingStrategy, { fn has_value(self, expected: X) -> Self { @@ -112,11 +120,10 @@ where } } -impl AssertHasValue for Spec<'_, &Result, R> +impl AssertHasValue for Spec<'_, &Result, D, R> where - T: PartialEq + Debug, - E: Debug, - X: Debug, + T: PartialEq, + D: Represent + Represent + Represent, R: FailingStrategy, { fn has_value(self, expected: X) -> Self { @@ -124,11 +131,10 @@ where } } -impl AssertHasError for Spec<'_, Result, R> +impl AssertHasError for Spec<'_, Result, D, R> where - T: Debug, - E: PartialEq + Debug, - X: Debug, + E: PartialEq, + D: Represent + Represent + Represent, R: FailingStrategy, { fn has_error(self, expected: X) -> Self { @@ -136,11 +142,10 @@ where } } -impl AssertHasError for Spec<'_, &Result, R> +impl AssertHasError for Spec<'_, &Result, D, R> where - T: Debug, - E: PartialEq + Debug, - X: Debug, + E: PartialEq, + D: Represent + Represent + Represent, R: FailingStrategy, { fn has_error(self, expected: X) -> Self { @@ -148,54 +153,65 @@ where } } -impl<'a, T, E, X, R> AssertHasErrorMessage for Spec<'a, Result, R> +impl<'a, T, E, X, D, R> AssertHasErrorMessage for Spec<'a, Result, D, R> where - T: Debug, E: Display, X: Debug, String: PartialEq, + D: Represent + Represent, R: FailingStrategy, { - type ErrorMessage = Spec<'a, String, R>; + type ErrorMessage = Spec<'a, String, DebugRepresentation, R>; fn has_error_message(self, expected: X) -> Self::ErrorMessage { - self.mapping(|result| match result { + let subject = match self.subject() { + Ok(value) => Ok(format!( + "Ok({})", + Represented::from((value, self.representation())) + )), + Err(error) => Err(error.to_string()), + }; + self.mapping(|_result| match subject { Ok(value) => panic!( - r"expected the subject to be `Err(_)` with message {expected:?}, but was `Ok({value:?})`" + r"expected the subject to be `Err(_)` with message {expected:?}, but was `{value}`" ), - Err(error) => { - error.to_string() - }, - }).expecting(is_equal_to(expected)) + Err(error) => error, + }) + .expecting(is_equal_to(expected)) } } -impl<'a, T, E, X, R> AssertHasErrorMessage for Spec<'a, &Result, R> +impl<'a, T, E, X, D, R> AssertHasErrorMessage for Spec<'a, &Result, D, R> where - T: Debug, E: Display, X: Debug, String: PartialEq, + D: Represent, R: FailingStrategy, { - type ErrorMessage = Spec<'a, String, R>; + type ErrorMessage = Spec<'a, String, DebugRepresentation, R>; fn has_error_message(self, expected: X) -> Self::ErrorMessage { - self.mapping(|result| match result { + let subject = match self.subject() { + Ok(value) => Ok(format!( + "Ok({:?})", + Represented::from((value, self.representation())) + )), + Err(error) => Err(error.to_string()), + }; + self.mapping(|_result| match subject { Ok(value) => panic!( - r"expected the subject to be `Err(_)` with message {expected:?}, but was `Ok({value:?})`" + r"expected the subject to be `Err(_)` with message {expected:?}, but was `{value}`" ), - Err(error) => { - error.to_string() - }, - }).expecting(is_equal_to(expected)) + Err(error) => error, + }) + .expecting(is_equal_to(expected)) } } -impl Expectation> for IsOk +impl Expectation, D> for IsOk where - T: Debug, - E: Debug, + D: Represent + Represent, { fn test(&mut self, subject: &Result) -> bool { subject.is_ok() @@ -206,21 +222,31 @@ where expression: &Expression<'_>, actual: &Result, _inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { - let expected = Ok::<_, Unknown>(Unknown); - let marked_actual = mark_unexpected(actual, format); - let marked_expected = mark_missing(&expected, format); + let marked_actual = match actual { + Ok(value) => mark_unexpected( + &format!("Ok({})", Represented::from((value, representation))), + &DisplayRepresentation, + format, + ), + Err(error) => mark_unexpected( + &format!("Err({})", Represented::from((error, representation))), + &DisplayRepresentation, + format, + ), + }; + let marked_expected = mark_missing(&"Ok(_)", &DisplayRepresentation, format); format!( - "expected {expression} to be {expected:?}\n but was: {marked_actual}\n expected: {marked_expected}" + "expected {expression} to be Ok(_)\n but was: {marked_actual}\n expected: {marked_expected}" ) } } -impl Expectation> for IsErr +impl Expectation, D> for IsErr where - T: Debug, - E: Debug, + D: Represent + Represent, { fn test(&mut self, subject: &Result) -> bool { subject.is_err() @@ -231,24 +257,34 @@ where expression: &Expression<'_>, actual: &Result, _inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { - let expected = Err::(Unknown); - let marked_actual = mark_unexpected(actual, format); - let marked_expected = mark_missing(&expected, format); + let marked_actual = match actual { + Ok(value) => mark_unexpected( + &format!("Ok({})", Represented::from((value, representation))), + &DisplayRepresentation, + format, + ), + Err(error) => mark_unexpected( + &format!("Err({})", Represented::from((error, representation))), + &DisplayRepresentation, + format, + ), + }; + let marked_expected = mark_missing(&"Err(_)", &DisplayRepresentation, format); format!( - "expected {expression} to be {expected:?}\n but was: {marked_actual}\n expected: {marked_expected}" + "expected {expression} to be Err(_)\n but was: {marked_actual}\n expected: {marked_expected}" ) } } -impl Expectation<&Result> for IsOk +impl Expectation<&Result, D> for IsOk where - T: Debug, - E: Debug, + D: Represent + Represent, { fn test(&mut self, subject: &&Result) -> bool { - >>::test(self, subject) + , D>>::test(self, subject) } fn message( @@ -256,19 +292,26 @@ where expression: &Expression<'_>, actual: &&Result, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { - >>::message(self, expression, actual, inverted, format) + , D>>::message( + self, + expression, + actual, + inverted, + representation, + format, + ) } } -impl Expectation<&Result> for IsErr +impl Expectation<&Result, D> for IsErr where - T: Debug, - E: Debug, + D: Represent + Represent, { fn test(&mut self, subject: &&Result) -> bool { - >>::test(self, subject) + , D>>::test(self, subject) } fn message( @@ -276,17 +319,24 @@ where expression: &Expression<'_>, actual: &&Result, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { - >>::message(self, expression, actual, inverted, format) + , D>>::message( + self, + expression, + actual, + inverted, + representation, + format, + ) } } -impl Expectation> for HasValue +impl Expectation, D> for HasValue where - T: PartialEq + Debug, - E: Debug, - X: Debug, + T: PartialEq, + D: Represent + Represent + Represent, { fn test(&mut self, subject: &Result) -> bool { subject.as_ref().is_ok_and(|value| value == &self.expected) @@ -297,26 +347,42 @@ where expression: &Expression<'_>, actual: &Result, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let not = if inverted { "not " } else { "" }; let expected = &self.expected; - let marked_actual = mark_unexpected(actual, format); - let marked_expected = mark_missing(&Ok::<_, E>(expected), format); + let marked_actual = match actual { + Ok(value) => mark_unexpected( + &format!("Ok({:?})", Represented::from((value, representation))), + &DisplayRepresentation, + format, + ), + Err(error) => mark_unexpected( + &format!("Err({:?})", Represented::from((error, representation))), + &DisplayRepresentation, + format, + ), + }; + let marked_expected = mark_missing( + &format!("Ok({:?})", Represented::from((expected, representation))), + &DisplayRepresentation, + format, + ); + let represented_expected = Represented::from((expected, representation)); format!( - "expected {expression} to be ok {not}containing {expected:?}\n but was: {marked_actual}\n expected: {not}{marked_expected}" + "expected {expression} to be ok {not}containing {represented_expected:?}\n but was: {marked_actual}\n expected: {not}{marked_expected}" ) } } -impl Expectation<&Result> for HasValue +impl Expectation<&Result, D> for HasValue where - T: PartialEq + Debug, - E: Debug, - X: Debug, + T: PartialEq, + D: Represent + Represent + Represent, { fn test(&mut self, subject: &&Result) -> bool { - >>::test(self, subject) + , D>>::test(self, subject) } fn message( @@ -324,17 +390,24 @@ where expression: &Expression<'_>, actual: &&Result, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { - >>::message(self, expression, actual, inverted, format) + , D>>::message( + self, + expression, + actual, + inverted, + representation, + format, + ) } } -impl Expectation> for HasError +impl Expectation, D> for HasError where - T: Debug, - E: PartialEq + Debug, - X: Debug, + E: PartialEq, + D: Represent + Represent + Represent, { fn test(&mut self, subject: &Result) -> bool { subject.as_ref().is_err_and(|err| err == &self.expected) @@ -345,28 +418,44 @@ where expression: &Expression<'_>, actual: &Result, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let not = if inverted { "not " } else { "" }; let expected = &self.expected; - let marked_actual = mark_unexpected(actual, format); - let marked_expected = mark_missing(&Err::(expected), format); + let marked_actual = match actual { + Ok(value) => mark_unexpected( + &format!("Ok({:?})", Represented::from((value, representation))), + &DisplayRepresentation, + format, + ), + Err(error) => mark_unexpected( + &format!("Err({:?})", Represented::from((error, representation))), + &DisplayRepresentation, + format, + ), + }; + let marked_expected = mark_missing( + &format!("Err({:?})", Represented::from((expected, representation))), + &DisplayRepresentation, + format, + ); + let represented_expected = Represented::from((expected, representation)); format!( - "expected {expression} to be an error {not}containing {expected:?}\n but was: {marked_actual}\n expected: {not}{marked_expected}" + "expected {expression} to be an error {not}containing {represented_expected:?}\n but was: {marked_actual}\n expected: {not}{marked_expected}" ) } } impl Invertible for HasError {} -impl Expectation<&Result> for HasError +impl Expectation<&Result, D> for HasError where - T: Debug, - E: PartialEq + Debug, - X: Debug, + E: PartialEq, + D: Represent + Represent + Represent, { fn test(&mut self, subject: &&Result) -> bool { - >>::test(self, subject) + , D>>::test(self, subject) } fn message( @@ -374,9 +463,17 @@ where expression: &Expression<'_>, actual: &&Result, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { - >>::message(self, expression, actual, inverted, format) + , D>>::message( + self, + expression, + actual, + inverted, + representation, + format, + ) } } diff --git a/src/spec/mod.rs b/src/spec/mod.rs index 6043fda..df44bb3 100644 --- a/src/spec/mod.rs +++ b/src/spec/mod.rs @@ -8,6 +8,7 @@ use crate::expectations::satisfies; use crate::recursive_comparison::RecursiveComparison; use crate::std::any; use crate::std::borrow::{Borrow, Cow, ToOwned}; +use crate::std::boxed::Box; use crate::std::cmp::Ordering; use crate::std::error::Error as StdError; use crate::std::fmt::{self, Debug, Display}; @@ -17,6 +18,7 @@ use crate::std::slice; use crate::std::string::{String, ToString}; use crate::std::vec; use crate::std::vec::Vec; + #[cfg(feature = "panic")] use crate::std::{cell::RefCell, rc::Rc}; @@ -246,7 +248,7 @@ macro_rules! verify_that_code { /// .is_equal_to(42); /// ``` #[track_caller] -pub fn assert_that<'a, S>(subject: S) -> Spec<'a, S, PanicOnFail> { +pub fn assert_that<'a, S>(subject: S) -> Spec<'a, S, DebugRepresentation, PanicOnFail> { #[cfg(not(feature = "colored"))] { Spec::new(subject, PanicOnFail) @@ -313,7 +315,7 @@ pub fn assert_that<'a, S>(subject: S) -> Spec<'a, S, PanicOnFail> { /// ]); /// ``` #[track_caller] -pub fn verify_that<'a, S>(subject: S) -> Spec<'a, S, CollectFailures> { +pub fn verify_that<'a, S>(subject: S) -> Spec<'a, S, DebugRepresentation, CollectFailures> { Spec::new(subject, CollectFailures) } @@ -347,7 +349,7 @@ pub fn verify_that<'a, S>(subject: S) -> Spec<'a, S, CollectFailures> { /// ``` #[cfg(feature = "panic")] #[cfg_attr(docsrs, doc(cfg(feature = "panic")))] -pub fn assert_that_code<'a, S>(code: S) -> Spec<'a, Code, PanicOnFail> +pub fn assert_that_code<'a, S>(code: S) -> Spec<'a, Code, DebugRepresentation, PanicOnFail> where S: FnOnce(), { @@ -417,7 +419,7 @@ where /// ``` #[cfg(feature = "panic")] #[cfg_attr(docsrs, doc(cfg(feature = "panic")))] -pub fn verify_that_code<'a, S>(code: S) -> Spec<'a, Code, CollectFailures> +pub fn verify_that_code<'a, S>(code: S) -> Spec<'a, Code, DebugRepresentation, CollectFailures> where S: FnOnce(), { @@ -431,7 +433,7 @@ where /// expected property. In case the test of the expectation fails, the /// `message()` method is called to form an expectation-specific failure /// message. -pub trait Expectation { +pub trait Expectation { /// Verifies whether the actual subject fulfills the expected property. fn test(&mut self, subject: &S) -> bool; @@ -441,6 +443,7 @@ pub trait Expectation { expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String; } @@ -664,7 +667,7 @@ impl PartialOrd for Location<'_> { /// /// In case of the [`CollectFailures`] failing strategy, the [`AssertFailure`]s /// are collected in this struct. -pub struct Spec<'a, S, R> { +pub struct Spec<'a, S, D, R> { subject: S, expression: Expression<'a>, description: Option>, @@ -672,9 +675,10 @@ pub struct Spec<'a, S, R> { failures: Vec, diff_format: DiffFormat, failing_strategy: R, + representation: D, } -impl Spec<'_, S, R> { +impl Spec<'_, S, D, R> { /// Returns the subject. pub fn subject(&self) -> &S { &self.subject @@ -699,9 +703,15 @@ impl Spec<'_, S, R> { pub fn failing_strategy(&self) -> &R { &self.failing_strategy } + + /// Returns the representation used for displaying values in failure + /// reports. + pub fn representation(&self) -> &D { + &self.representation + } } -impl<'a, S, R> Spec<'a, S, R> { +impl Spec<'_, S, DebugRepresentation, R> { /// Constructs a new `Spec` for the given subject and with the specified /// failing strategy. /// @@ -717,9 +727,12 @@ impl<'a, S, R> Spec<'a, S, R> { failures: vec![], diff_format: colored::DIFF_FORMAT_NO_HIGHLIGHT, failing_strategy, + representation: DebugRepresentation, } } +} +impl<'a, S, D, R> Spec<'a, S, D, R> { /// Sets the subject name or expression for this assertion. #[must_use = "a spec does nothing unless an assertion method is called"] pub fn named(mut self, subject_name: impl Into>) -> Self { @@ -793,7 +806,7 @@ impl<'a, S, R> Spec<'a, S, R> { #[cfg(feature = "recursive")] #[cfg_attr(docsrs, doc(cfg(feature = "recursive")))] #[must_use = "the returned `RecursiveComparison` does nothing unless an assertion method like `is_equal_to` is called"] - pub fn using_recursive_comparison(self) -> RecursiveComparison<'a, S, R> { + pub fn using_recursive_comparison(self) -> RecursiveComparison<'a, S, D, R> { RecursiveComparison::new(self) } @@ -870,7 +883,7 @@ impl<'a, S, R> Spec<'a, S, R> { self, property_name: impl Into>, extract: F, - ) -> DerivedSpec<'a, Self, U> + ) -> DerivedSpec<'a, Self, U, DebugRepresentation> where F: FnOnce(&S) -> &B, B: ToOwned + ?Sized, @@ -969,7 +982,7 @@ impl<'a, S, R> Spec<'a, S, R> { self, property_name: impl Into>, extract: F, - ) -> Spec<'a, U, R> + ) -> Spec<'a, U, DebugRepresentation, R> where F: FnOnce(S) -> U, { @@ -985,6 +998,7 @@ impl<'a, S, R> Spec<'a, S, R> { failures: self.failures, diff_format: self.diff_format, failing_strategy: self.failing_strategy, + representation: DebugRepresentation, } } @@ -1025,7 +1039,7 @@ impl<'a, S, R> Spec<'a, S, R> { /// assertion. So we map the subject of the type `Point` to a tuple of its /// fields. #[must_use = "a spec does nothing unless an assertion method is called"] - pub fn mapping(self, map: F) -> Spec<'a, U, R> + pub fn mapping(self, map: F) -> Spec<'a, U, DebugRepresentation, R> where F: FnOnce(S) -> U, { @@ -1037,19 +1051,20 @@ impl<'a, S, R> Spec<'a, S, R> { failures: self.failures, diff_format: self.diff_format, failing_strategy: self.failing_strategy, + representation: DebugRepresentation, } } } -impl<'a, I, R> AssertElements<'a, I> for Spec<'a, I, R> +impl<'a, I, D, R> AssertElements<'a, I> for Spec<'a, I, D, R> where I: IntoIterator, { - type Output = Spec<'a, (), R>; + type Output = Spec<'a, (), DebugRepresentation, R>; fn each_element(mut self, assert: A) -> Self::Output where - A: Fn(Spec<'a, ::Item, CollectFailures>) -> B, + A: Fn(Spec<'a, ::Item, DebugRepresentation, CollectFailures>) -> B, B: GetFailures, { let root_expression = &self.expression; @@ -1064,6 +1079,7 @@ where failures: vec![], diff_format: self.diff_format.clone(), failing_strategy: CollectFailures, + representation: DebugRepresentation, }; let failures = assert(element_spec).failures(); self.failures.extend(failures); @@ -1081,12 +1097,13 @@ where failures: self.failures, diff_format: self.diff_format, failing_strategy: self.failing_strategy, + representation: DebugRepresentation, } } fn any_element(mut self, assert: A) -> Self::Output where - A: Fn(Spec<'a, ::Item, CollectFailures>) -> B, + A: Fn(Spec<'a, ::Item, DebugRepresentation, CollectFailures>) -> B, B: GetFailures, { let root_expression = &self.expression; @@ -1102,6 +1119,7 @@ where failures: vec![], diff_format: self.diff_format.clone(), failing_strategy: CollectFailures, + representation: DebugRepresentation, }; let failures = assert(element_spec).failures(); if failures.is_empty() { @@ -1123,25 +1141,29 @@ where failures: self.failures, diff_format: self.diff_format, failing_strategy: self.failing_strategy, + representation: DebugRepresentation, } } } -impl<'a, I, R> Spec<'a, I, R> +#[allow(clippy::type_complexity)] +impl<'a, I, D, R> Spec<'a, I, D, R> where I: IntoIterator, + D: Clone, { pub(crate) fn extracting_ref_iter( self, property_name: impl Into>, extract: F, - ) -> DerivedSpec<'a, Spec<'a, Vec<::Item>, R>, Vec> + ) -> DerivedSpec<'a, Spec<'a, Vec<::Item>, D, R>, Vec, DebugRepresentation> where for<'b> F: Fn(slice::Iter<'b, ::Item>) -> Vec, { let property_name = Expression(property_name.into()); let diff_format = self.diff_format.clone(); - let orig_spec = self.mapping(Vec::from_iter); + let representation = self.representation().clone(); + let orig_spec = self.mapping(Vec::from_iter).represented_by(representation); let new_subject = extract(orig_spec.subject.iter()); DerivedSpec::new(orig_spec, new_subject, property_name, diff_format) } @@ -1161,7 +1183,7 @@ pub trait DoFail { fn do_fail_with_message(&mut self, message: impl Into); } -impl DoFail for Spec<'_, S, R> +impl DoFail for Spec<'_, S, D, R> where R: FailingStrategy, { @@ -1248,7 +1270,7 @@ pub trait SoftPanic { fn soft_panic(&self); } -impl SoftPanic for Spec<'_, S, CollectFailures> { +impl SoftPanic for Spec<'_, S, D, CollectFailures> { fn soft_panic(&self) { if !self.failures.is_empty() { PanicOnFail.do_fail_with(&self.failures); @@ -1347,7 +1369,7 @@ pub trait And { fn and(self) -> Self::Output; } -impl And for Spec<'_, S, R> { +impl And for Spec<'_, S, D, R> { type Output = Self; fn and(self) -> Self::Output { @@ -1387,7 +1409,7 @@ pub trait Satisfies { /// let failures = verify_that!(22).satisfies(is_odd).display_failures(); /// /// assert_that!(failures).contains_exactly([ - /// "expected 22 to satisfy the given predicate, but returned false\n" + /// "expected 22 to satisfy the given predicate, but returned false\n actual: 22\n" /// ]); /// ``` /// @@ -1442,8 +1464,9 @@ pub trait Satisfies { P: Fn(&S) -> bool; } -impl Satisfies for Spec<'_, S, R> +impl Satisfies for Spec<'_, S, D, R> where + D: Represent, R: FailingStrategy, { fn satisfies

(self, predicate: P) -> Self @@ -1463,7 +1486,7 @@ where /// Verify whether a subject meets the given expectation (impl of /// [`Expectation`]) and record a failure if it is not met. -pub trait Expecting { +pub trait Expecting { /// Asserts the given expectation. /// /// In case the expectation is not meet, the assertion fails, according to @@ -1485,17 +1508,22 @@ pub trait Expecting { /// ``` #[allow(clippy::needless_pass_by_value, clippy::return_self_not_must_use)] #[track_caller] - fn expecting(self, expectation: impl Expectation) -> Self; + fn expecting(self, expectation: impl Expectation) -> Self; } -impl Expecting for Spec<'_, S, R> +impl Expecting for Spec<'_, S, D, R> where R: FailingStrategy, { - fn expecting(mut self, mut expectation: impl Expectation) -> Self { + fn expecting(mut self, mut expectation: impl Expectation) -> Self { if !expectation.test(&self.subject) { - let message = - expectation.message(&self.expression, &self.subject, false, &self.diff_format); + let message = expectation.message( + &self.expression, + &self.subject, + false, + &self.representation, + &self.diff_format, + ); self.do_fail_with_message(message); } self @@ -1508,7 +1536,7 @@ pub trait GetLocation<'a> { fn location(&self) -> Option>; } -impl<'a, S, R> GetLocation<'a> for Spec<'a, S, R> { +impl<'a, S, D, R> GetLocation<'a> for Spec<'a, S, D, R> { fn location(&self) -> Option> { self.location } @@ -1526,7 +1554,7 @@ pub trait GetFailures { fn display_failures(&self) -> Vec; } -impl GetFailures for Spec<'_, S, R> { +impl GetFailures for Spec<'_, S, D, R> { fn has_failures(&self) -> bool { !self.failures.is_empty() } @@ -1668,20 +1696,30 @@ impl FailingStrategy for CollectFailures { /// /// ```no_run /// # use std::fmt::Debug; -/// # use asserting::spec::{DiffFormat, Expectation, Expression, Unknown}; +/// # use asserting::spec::{DiffFormat, Expectation, Expression, Represent, Represented, Unknown}; /// # struct IsOk; -/// impl Expectation> for IsOk +/// impl Expectation, D> for IsOk /// where -/// T: Debug, -/// E: Debug, +/// D: Represent + Represent, /// { /// fn test(&mut self, subject: &Result) -> bool { /// subject.is_ok() /// } /// -/// fn message(&self, expression: &Expression<'_>, actual: &Result, _inverted: bool, _format: &DiffFormat) -> String { +/// fn message( +/// &self, +/// expression: &Expression<'_>, +/// actual: &Result, +/// _inverted: bool, +/// representation: &D, +/// _format: &DiffFormat +/// ) -> String { +/// let represented_actual = match actual { +/// Ok(value) => format!("Ok({:?}", Represented::from((value, representation))), +/// Err(error) => format!("Err({:?}", Represented::from((error, representation))), +/// }; /// format!( -/// "expected {expression} is {:?}\n but was: {actual:?}\n expected: {:?}", +/// "expected {expression} is {:?}\n but was: {represented_actual:?}\n expected: {:?}", /// Ok::<_, Unknown>(Unknown), /// Ok::<_, Unknown>(Unknown), /// ) @@ -1732,5 +1770,276 @@ mod code { } } +/// Specify a custom representation that `asserting` shall use to format the +/// subject and the expected value in failure reports. +pub trait RepresentedBy

{ + /// The output type of this trait. + /// + /// Usually this is a [`Spec`] or a [`DerivedSpec`]. + type Output; + + /// Configure a custom representation that `asserting` shall use to format + /// the subject and the expected value in failure reports. + /// + /// The representation is a type that implements the [`Represent`] trait + /// for the type of the subject and the type of the expected value. + /// See the docs of the [`Represent`] trait for an example of implementing + /// a custom representation. + fn represented_by(self, representation: P) -> Self::Output; +} + +impl<'a, S, D, R, D2> RepresentedBy for Spec<'a, S, D, R> { + type Output = Spec<'a, S, D2, R>; + + fn represented_by(self, representation: D2) -> Self::Output { + Spec { + subject: self.subject, + expression: self.expression, + description: self.description, + location: self.location, + failures: self.failures, + diff_format: self.diff_format, + failing_strategy: self.failing_strategy, + representation, + } + } +} + +/// Specify an ad-hoc representation function or closure that `asserting` shall +/// use to format the subject and the expected value in failure reports. +pub trait RepresentedAs { + /// The type of the subject that shall be formatted. + type Subject; + /// The output type of this trait. + /// + /// Usually this is a [`Spec`] or a [`DerivedSpec`]. + type Output; + + /// Configure a representation function or closure that `asserting` shall + /// use to format the subject and the expected value in failure reports. + fn represented_as(self, representation: F) -> Self::Output + where + F: Fn(&Self::Subject, &mut fmt::Formatter<'_>) -> fmt::Result + 'static; +} + +impl<'a, S, D, R> RepresentedAs for Spec<'a, S, D, R> { + type Subject = S; + type Output = Spec<'a, S, AdHocRepresentation, R>; + + fn represented_as(self, representation: F) -> Self::Output + where + F: Fn(&S, &mut fmt::Formatter<'_>) -> fmt::Result + 'static, + { + self.represented_by(AdHocRepresentation(Box::new(representation))) + } +} + +/// A trait that defines how a type's value is printed in the failure report of +/// a failing assertion. +/// +/// With the use of this trait we can define the representation of actual and +/// expected values in failure reports. +/// +/// It defines one method: `represent`. The signature of this method is similar +/// to the `fmt`-method of the [`Debug`] and [`Display`] traits in the +/// standard library. +/// +/// To implement the representation of a type `Foo`, we first define a struct +/// (usually a unit struct), e.g. `FooRepresentation`. Then we implement this +/// trait for the "representation" struct (in this example `FooRepresentation`) +/// with our custom type as a type parameter. +/// +/// ```no_run +/// use asserting::prelude::*; +/// use core::fmt; +/// +/// #[derive(PartialEq)] +/// struct Foo { +/// bar: String, +/// baz: u16, +/// } +/// +/// #[derive(Clone, Copy)] +/// struct FooRepresentation; +/// +/// impl Represent for FooRepresentation { +/// fn represent(&self, value: &Foo, f: &mut fmt::Formatter<'_>) -> fmt::Result { +/// write!(f, "Foo {{ bar: {}, baz: {} }}", value.bar, value.baz) +/// } +/// } +/// ``` +/// +/// Assertions for values in containers (e.g. `Vec`) require that the +/// representation struct implements the `Clone` trait. That's why we derive +/// `Clone` and `Copy` for `FooRepresentation` in the example above. +/// +/// To make use of this representation, we have to tell `asserting` to use it by +/// calling the [`Spec::represented_by`] method, like so: +/// +/// ``` +/// use asserting::prelude::*; +/// use core::fmt; +/// # #[derive(PartialEq)] +/// # struct Foo { +/// # bar: String, +/// # baz: u16, +/// # } +/// # +/// # #[derive(Clone, Copy)] +/// # struct FooRepresentation; +/// # +/// # impl Represent for FooRepresentation { +/// # fn represent(&self, value: &Foo, f: &mut fmt::Formatter<'_>) -> fmt::Result { +/// # write!(f, "Foo {{ bar: {}, baz: {} }}", value.bar, value.baz) +/// # } +/// # } +/// +/// let foo = Foo { bar: "bar".to_string(), baz: 42 }; +/// +/// assert_that!(foo) +/// .represented_by(FooRepresentation) +/// .is_equal_to(Foo { bar: "bar".to_string(), baz: 42 }); +/// ``` +/// +/// This representation mechanic can be used to: +/// +/// 1. define a custom representation for a type that already implements `Debug` +/// but for testing purposes we want a different representation. +/// 2. write assertions for types that do not implement `Debug` and there are +/// some reasons why we cannot implement it. E.g., foreign types and the +/// orphan rule. +/// +/// It is only possible to configure one representation type per assertion +/// (that is per `assert_that!()...` statement). If the subject and the expected +/// value are not exactly of the same type (e.g., `String` and `str`), then +/// the representation must implement the [`Represent`] trait for both types, +/// the type of the subject and the type of the expected value. +/// +/// This crate provides a [`DebugRepresentation`] which can represent any type +/// that implements the `Debug` trait, and a [`DisplayRepresentation`] +/// which can represent any type that implements the `fmt::Display` trait. The +/// [`DebugRepresentation`] is used when no other representation is specified +/// by calling [`Spec::represented_by`]. +/// +/// For simple cases and/or we need a custom representation just for one or a +/// few tests, we can use an ad-hoc representation, which is a format function +/// given to the [`Spec::represented_as`] method. +pub trait Represent { + /// Formats the given value of type `T` as it should be represented in + /// failure reports. + /// + /// Implementations are very similar to those of the [`Debug`] and + /// [`Display`] traits of the standard library. + #[allow(clippy::missing_errors_doc)] + fn represent(&self, value: &T, f: &mut fmt::Formatter<'_>) -> fmt::Result; +} + +/// Combines a value and a representation for this value. +/// +/// The representation can be a type that implements [`Represent`] or an +/// [`AdHocRepresentation`]. +pub struct Represented<'t, 'd, T: ?Sized, D> { + /// The value of type `T`. + pub value: &'t T, + /// The representation to be used for the value. + pub representation: &'d D, +} + +impl Clone for Represented<'_, '_, T, D> { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Represented<'_, '_, T, D> {} + +impl<'t, 'd, T: ?Sized, D> From<(&'t T, &'d D)> for Represented<'t, 'd, T, D> { + fn from((value, representation): (&'t T, &'d D)) -> Self { + Represented { + value, + representation, + } + } +} + +impl Debug for Represented<'_, '_, T, D> +where + D: Represent, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.representation.represent(self.value, f) + } +} + +impl Display for Represented<'_, '_, T, D> +where + D: Represent, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.representation.represent(self.value, f) + } +} + +/// An ad-hoc representation formats a value by a format function or closure. +/// +/// Using a format function does not require defining a representation struct +/// and implementing the [`Represent`] trait for it. But the function has to +/// be written for all tests where an ad-hoc representation should be used. +/// +/// This is useful if we need a custom representation only for one or a few +/// tests, or we want different representations for different test cases. +/// Usually we will not use this struct directly in tests. Instead, we call the +/// [`Spec::represented_as`] method. `asserting` wraps the function into this +/// struct to store the representation function or closure internally. +/// +/// The representation function has a similar signature as the +/// [`represent`](Represent::represent) method of the [`Represent`] trait. +#[allow(clippy::type_complexity)] +pub struct AdHocRepresentation(pub Box) -> fmt::Result>); + +impl Represent for AdHocRepresentation { + fn represent(&self, value: &T, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0(value, f) + } +} + +/// A representation that can format all values of any type `T` that implements +/// the [`Debug`] trait of the standard library. +/// +/// The implementation of the [`Represent`] for this representation just +/// delegates to the implementation of the `Debug` trait. +/// +/// This is the default representation used by `asserting` as long as we do not +/// specify a different representation by calling [`Spec::represented_by`] or +/// use an ad-hoc representation by calling the [`Spec::represented_as`] method. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct DebugRepresentation; + +impl Represent for DebugRepresentation +where + T: ?Sized + Debug, +{ + fn represent(&self, value: &T, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Debug::fmt(value, f) + } +} + +/// A representation that can format all values of any type `T` that implements +/// the [`Display`] trait of the standard library. +/// +/// The implementation of the [`Represent`] for this representation just +/// delegates to the implementation of the `Display` trait. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct DisplayRepresentation; + +impl Represent for DisplayRepresentation +where + T: ?Sized + Display, +{ + fn represent(&self, value: &T, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Display::fmt(value, f) + } +} + #[cfg(test)] mod tests; diff --git a/src/string/mod.rs b/src/string/mod.rs index f77ed67..7757998 100644 --- a/src/string/mod.rs +++ b/src/string/mod.rs @@ -2,19 +2,18 @@ use crate::assertions::{AssertStringContainsAnyOf, AssertStringPattern}; use crate::colored::{ - mark_missing, mark_missing_char, mark_missing_string, - mark_selected_chars_in_string_as_unexpected, mark_selected_items_in_collection, - mark_unexpected_char_in_string, mark_unexpected_string, mark_unexpected_substring_in_string, + mark_missing, mark_selected_chars_in_string_as_unexpected, mark_selected_items_in_collection, + mark_unexpected, mark_unexpected_char_in_string, mark_unexpected_substring_in_string, }; use crate::expectations::{ - StringContains, StringContainsAnyOf, StringEndsWith, StringStartWith, not, string_contains, + StringContains, StringContainsAnyOf, StringEndsWith, StringStartsWith, not, string_contains, string_contains_any_of, string_ends_with, string_starts_with, }; use crate::properties::{CharCountProperty, DefinedOrderProperty, IsEmptyProperty, LengthProperty}; use crate::spec::{ - DiffFormat, Expectation, Expecting, Expression, FailingStrategy, Invertible, Spec, + DiffFormat, DisplayRepresentation, Expectation, Expecting, Expression, FailingStrategy, + Invertible, Represent, Represented, Spec, }; -use crate::std::fmt::Debug; use crate::std::str::Chars; use crate::std::{ format, @@ -66,9 +65,10 @@ impl DefinedOrderProperty for Chars<'_> {} // see issue [#27721](https://github.com/rust-lang/rust/issues/27721). // Maybe we keep the implementations for a long time to support an earlier MSRV. -impl<'a, S, R> AssertStringPattern<&'a str> for Spec<'a, S, R> +impl<'a, S, D, R> AssertStringPattern<&'a str> for Spec<'a, S, D, R> where - S: 'a + AsRef + Debug, + S: 'a + AsRef, + D: Represent, R: FailingStrategy, { fn contains(self, pattern: &'a str) -> Self { @@ -96,9 +96,10 @@ where } } -impl<'a, S, R> AssertStringPattern for Spec<'a, S, R> +impl<'a, S, D, R> AssertStringPattern for Spec<'a, S, D, R> where - S: 'a + AsRef + Debug, + S: 'a + AsRef, + D: Represent, R: FailingStrategy, { fn contains(self, pattern: String) -> Self { @@ -126,9 +127,10 @@ where } } -impl<'a, S, R> AssertStringPattern for Spec<'a, S, R> +impl<'a, S, D, R> AssertStringPattern for Spec<'a, S, D, R> where - S: 'a + AsRef + Debug, + S: 'a + AsRef, + D: Represent + Represent, R: FailingStrategy, { fn contains(self, expected: char) -> Self { @@ -156,9 +158,10 @@ where } } -impl Expectation for StringContains<&str> +impl Expectation for StringContains<&str> where - S: AsRef + Debug, + S: AsRef, + D: Represent, { fn test(&mut self, subject: &S) -> bool { subject.as_ref().contains(self.expected) @@ -169,6 +172,7 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + _representation: &D, format: &DiffFormat, ) -> String { let (not, marked_actual) = if inverted { @@ -176,10 +180,10 @@ where mark_unexpected_substring_in_string(actual.as_ref(), self.expected, format); ("not ", marked_actual) } else { - let marked_actual = mark_unexpected_string(actual.as_ref(), format); + let marked_actual = mark_unexpected(actual.as_ref(), &DisplayRepresentation, format); ("", marked_actual) }; - let marked_expected = mark_missing_string(self.expected, format); + let marked_expected = mark_missing(self.expected, &DisplayRepresentation, format); format!( "expected {expression} to {not}contain {:?}\n but was: \"{marked_actual}\"\n expected: {not}\"{marked_expected}\"", self.expected, @@ -189,9 +193,10 @@ where impl Invertible for StringContains<&str> {} -impl Expectation for StringContains +impl Expectation for StringContains where - S: AsRef + Debug, + S: AsRef, + D: Represent, { fn test(&mut self, subject: &S) -> bool { subject.as_ref().contains(&self.expected) @@ -202,6 +207,7 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + _representation: &D, format: &DiffFormat, ) -> String { let (not, marked_actual) = if inverted { @@ -209,10 +215,10 @@ where mark_unexpected_substring_in_string(actual.as_ref(), &self.expected, format); ("not ", marked_actual) } else { - let marked_actual = mark_unexpected_string(actual.as_ref(), format); + let marked_actual = mark_unexpected(actual.as_ref(), &DisplayRepresentation, format); ("", marked_actual) }; - let marked_expected = mark_missing_string(&self.expected, format); + let marked_expected = mark_missing(&self.expected[..], &DisplayRepresentation, format); format!( "expected {expression} to {not}contain {:?}\n but was: \"{marked_actual}\"\n expected: {not}\"{marked_expected}\"", self.expected, @@ -222,9 +228,10 @@ where impl Invertible for StringContains {} -impl Expectation for StringContains +impl Expectation for StringContains where - S: AsRef + Debug, + S: AsRef, + D: Represent + Represent, { fn test(&mut self, subject: &S) -> bool { subject.as_ref().contains(self.expected) @@ -235,6 +242,7 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + _representation: &D, format: &DiffFormat, ) -> String { let (not, marked_actual) = if inverted { @@ -242,10 +250,10 @@ where mark_unexpected_char_in_string(actual.as_ref(), self.expected, format); ("not ", marked_actual) } else { - let marked_actual = mark_unexpected_string(actual.as_ref(), format); + let marked_actual = mark_unexpected(actual.as_ref(), &DisplayRepresentation, format); ("", marked_actual) }; - let marked_expected = mark_missing_char(self.expected, format); + let marked_expected = mark_missing(&self.expected, &DisplayRepresentation, format); format!( "expected {expression} to {not}contain {:?}\n but was: \"{marked_actual}\"\n expected: {not}'{marked_expected}'", self.expected, @@ -255,9 +263,10 @@ where impl Invertible for StringContains {} -impl Expectation for StringStartWith<&str> +impl Expectation for StringStartsWith<&str> where - S: AsRef + Debug, + S: AsRef, + D: Represent, { fn test(&mut self, subject: &S) -> bool { subject.as_ref().starts_with(self.expected) @@ -268,6 +277,7 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + _representation: &D, format: &DiffFormat, ) -> String { let not = if inverted { "not " } else { "" }; @@ -282,8 +292,9 @@ where .chars() .skip(expected_char_len) .collect::(); - let marked_actual_start = mark_unexpected_string(&actual_start, format); - let marked_expected = mark_missing_string(self.expected, format); + let marked_actual_start = + mark_unexpected(&actual_start[..], &DisplayRepresentation, format); + let marked_expected = mark_missing(self.expected, &DisplayRepresentation, format); format!( "expected {expression} to {not}start with {:?}\n but was: \"{marked_actual_start}{actual_rest}\"\n expected: {not}\"{marked_expected}\"", self.expected, @@ -291,11 +302,12 @@ where } } -impl Invertible for StringStartWith<&str> {} +impl Invertible for StringStartsWith<&str> {} -impl Expectation for StringStartWith +impl Expectation for StringStartsWith where - S: AsRef + Debug, + S: AsRef, + D: Represent, { fn test(&mut self, subject: &S) -> bool { subject.as_ref().starts_with(&self.expected) @@ -306,6 +318,7 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + _representation: &D, format: &DiffFormat, ) -> String { let not = if inverted { "not " } else { "" }; @@ -320,8 +333,8 @@ where .chars() .skip(expected_char_len) .collect::(); - let marked_actual_start = mark_unexpected_string(&actual_start, format); - let marked_expected = mark_missing_string(&self.expected, format); + let marked_actual_start = mark_unexpected(&actual_start, &DisplayRepresentation, format); + let marked_expected = mark_missing(&self.expected[..], &DisplayRepresentation, format); format!( "expected {expression} to {not}start with {:?}\n but was: \"{marked_actual_start}{actual_rest}\"\n expected: {not}\"{marked_expected}\"", self.expected, @@ -329,11 +342,12 @@ where } } -impl Invertible for StringStartWith {} +impl Invertible for StringStartsWith {} -impl Expectation for StringStartWith +impl Expectation for StringStartsWith where - S: AsRef + Debug, + S: AsRef, + D: Represent + Represent, { fn test(&mut self, subject: &S) -> bool { subject.as_ref().starts_with(self.expected) @@ -344,13 +358,15 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + _representation: &D, format: &DiffFormat, ) -> String { let not = if inverted { "not " } else { "" }; let actual_first_char = actual.as_ref().chars().take(1).collect::(); let actual_rest = actual.as_ref().chars().skip(1).collect::(); - let marked_actual_start = mark_unexpected_string(&actual_first_char, format); - let marked_expected = mark_missing_char(self.expected, format); + let marked_actual_start = + mark_unexpected(&actual_first_char, &DisplayRepresentation, format); + let marked_expected = mark_missing(&self.expected, &DisplayRepresentation, format); format!( "expected {expression} to {not}start with {:?}\n but was: \"{marked_actual_start}{actual_rest}\"\n expected: {not}'{marked_expected}'", self.expected, @@ -358,11 +374,12 @@ where } } -impl Invertible for StringStartWith {} +impl Invertible for StringStartsWith {} -impl Expectation for StringEndsWith<&str> +impl Expectation for StringEndsWith<&str> where - S: AsRef + Debug, + S: AsRef, + D: Represent, { fn test(&mut self, subject: &S) -> bool { subject.as_ref().ends_with(self.expected) @@ -373,6 +390,7 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + _representation: &D, format: &DiffFormat, ) -> String { let not = if inverted { "not " } else { "" }; @@ -389,8 +407,8 @@ where .chars() .skip(split_point) .collect::(); - let marked_actual_end = mark_unexpected_string(&actual_end, format); - let marked_expected = mark_missing_string(self.expected, format); + let marked_actual_end = mark_unexpected(&actual_end, &DisplayRepresentation, format); + let marked_expected = mark_missing(self.expected, &DisplayRepresentation, format); format!( "expected {expression} to {not}end with {:?}\n but was: \"{actual_start}{marked_actual_end}\"\n expected: {not}\"{marked_expected}\"", self.expected, @@ -400,9 +418,10 @@ where impl Invertible for StringEndsWith<&str> {} -impl Expectation for StringEndsWith +impl Expectation for StringEndsWith where - S: AsRef + Debug, + S: AsRef, + D: Represent, { fn test(&mut self, subject: &S) -> bool { subject.as_ref().ends_with(&self.expected) @@ -413,6 +432,7 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + _representation: &D, format: &DiffFormat, ) -> String { let not = if inverted { "not " } else { "" }; @@ -429,8 +449,8 @@ where .chars() .skip(split_point) .collect::(); - let marked_actual_end = mark_unexpected_string(&actual_end, format); - let marked_expected = mark_missing_string(&self.expected, format); + let marked_actual_end = mark_unexpected(&actual_end, &DisplayRepresentation, format); + let marked_expected = mark_missing(&self.expected[..], &DisplayRepresentation, format); format!( "expected {expression} to {not}end with {:?}\n but was: \"{actual_start}{marked_actual_end}\"\n expected: {not}\"{marked_expected}\"", self.expected, @@ -440,9 +460,10 @@ where impl Invertible for StringEndsWith {} -impl Expectation for StringEndsWith +impl Expectation for StringEndsWith where - S: AsRef + Debug, + S: AsRef, + D: Represent + Represent, { fn test(&mut self, subject: &S) -> bool { subject.as_ref().ends_with(self.expected) @@ -453,6 +474,7 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + _representation: &D, format: &DiffFormat, ) -> String { let not = if inverted { "not " } else { "" }; @@ -464,8 +486,8 @@ where .unwrap_or_default(); let mut actual_start = actual.as_ref().to_string(); actual_start.pop(); - let marked_actual_end = mark_unexpected_string(&actual_last_char, format); - let marked_expected = mark_missing_char(self.expected, format); + let marked_actual_end = mark_unexpected(&actual_last_char, &DisplayRepresentation, format); + let marked_expected = mark_missing(&self.expected, &DisplayRepresentation, format); format!( "expected {expression} to {not}end with {:?}\n but was: \"{actual_start}{marked_actual_end}\"\n expected: {not}'{marked_expected}'", self.expected, @@ -481,9 +503,10 @@ impl Invertible for StringEndsWith {} // assertion for array/slice of chars as expected value, but not the // [`AssertContains`] assertion. -impl<'a, S, R> AssertStringContainsAnyOf<&'a [char]> for Spec<'a, S, R> +impl<'a, S, D, R> AssertStringContainsAnyOf<&'a [char]> for Spec<'a, S, D, R> where - S: 'a + AsRef + Debug, + S: 'a + AsRef, + D: Represent + Represent, R: FailingStrategy, { fn contains_any_of(self, expected: &'a [char]) -> Self { @@ -495,9 +518,10 @@ where } } -impl<'a, S, R, const N: usize> AssertStringContainsAnyOf<[char; N]> for Spec<'a, S, R> +impl<'a, S, D, R, const N: usize> AssertStringContainsAnyOf<[char; N]> for Spec<'a, S, D, R> where - S: 'a + AsRef + Debug, + S: 'a + AsRef, + D: Represent + Represent, R: FailingStrategy, { fn contains_any_of(self, expected: [char; N]) -> Self { @@ -509,9 +533,10 @@ where } } -impl<'a, S, R, const N: usize> AssertStringContainsAnyOf<&'a [char; N]> for Spec<'a, S, R> +impl<'a, S, D, R, const N: usize> AssertStringContainsAnyOf<&'a [char; N]> for Spec<'a, S, D, R> where - S: 'a + AsRef + Debug, + S: 'a + AsRef, + D: Represent + Represent, R: FailingStrategy, { fn contains_any_of(self, expected: &'a [char; N]) -> Self { @@ -523,9 +548,10 @@ where } } -impl Expectation for StringContainsAnyOf<&[char]> +impl Expectation for StringContainsAnyOf<&[char]> where - S: AsRef + Debug, + S: AsRef, + D: Represent + Represent, { fn test(&mut self, subject: &S) -> bool { subject.as_ref().contains(self.expected) @@ -536,6 +562,7 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let (not, marked_actual, marked_expected) = if inverted { @@ -564,13 +591,23 @@ where let marked_expected = mark_selected_items_in_collection( self.expected, &found_in_expected, + representation, format, mark_missing, ); ("not ", marked_actual, marked_expected) } else { - let marked_actual = mark_unexpected_string(actual.as_ref(), format); - let marked_expected = mark_missing(&self.expected, format); + let marked_actual = mark_unexpected(actual.as_ref(), &DisplayRepresentation, format); + let represented_expected = self + .expected + .iter() + .map(|c| Represented::from((c, representation))) + .collect::>(); + let marked_expected = mark_missing( + &format!("{represented_expected:?}"), + &DisplayRepresentation, + format, + ); ("", marked_actual, marked_expected) }; format!( @@ -582,9 +619,10 @@ where impl Invertible for StringContainsAnyOf<&[char]> {} -impl Expectation for StringContainsAnyOf<[char; N]> +impl Expectation for StringContainsAnyOf<[char; N]> where - S: AsRef + Debug, + S: AsRef, + D: Represent + Represent, { fn test(&mut self, subject: &S) -> bool { subject.as_ref().contains(self.expected) @@ -595,6 +633,7 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let (not, marked_actual, marked_expected) = if inverted { @@ -623,13 +662,23 @@ where let marked_expected = mark_selected_items_in_collection( &self.expected, &found_in_expected, + representation, format, mark_missing, ); ("not ", marked_actual, marked_expected) } else { - let marked_actual = mark_unexpected_string(actual.as_ref(), format); - let marked_expected = mark_missing(&self.expected, format); + let marked_actual = mark_unexpected(actual.as_ref(), &DisplayRepresentation, format); + let represented_expected = self + .expected + .iter() + .map(|c| Represented::from((c, representation))) + .collect::>(); + let marked_expected = mark_missing( + &format!("{represented_expected:?}"), + &DisplayRepresentation, + format, + ); ("", marked_actual, marked_expected) }; format!( @@ -641,9 +690,10 @@ where impl Invertible for StringContainsAnyOf<[char; N]> {} -impl Expectation for StringContainsAnyOf<&[char; N]> +impl Expectation for StringContainsAnyOf<&[char; N]> where - S: AsRef + Debug, + S: AsRef, + D: Represent + Represent, { fn test(&mut self, subject: &S) -> bool { subject.as_ref().contains(self.expected) @@ -654,6 +704,7 @@ where expression: &Expression<'_>, actual: &S, inverted: bool, + representation: &D, format: &DiffFormat, ) -> String { let (not, marked_actual, marked_expected) = if inverted { @@ -682,13 +733,23 @@ where let marked_expected = mark_selected_items_in_collection( self.expected, &found_in_expected, + representation, format, mark_missing, ); ("not ", marked_actual, marked_expected) } else { - let marked_actual = mark_unexpected_string(actual.as_ref(), format); - let marked_expected = mark_missing(&self.expected, format); + let marked_actual = mark_unexpected(actual.as_ref(), &DisplayRepresentation, format); + let represented_expected = self + .expected + .iter() + .map(|c| Represented::from((c, representation))) + .collect::>(); + let marked_expected = mark_missing( + &format!("{represented_expected:?}"), + &DisplayRepresentation, + format, + ); ("", marked_actual, marked_expected) }; format!( @@ -703,18 +764,19 @@ impl Invertible for StringContainsAnyOf<&[char; N]> {} #[cfg(feature = "regex")] mod regex { use crate::assertions::AssertStringMatches; - use crate::colored::{mark_missing_string, mark_unexpected_string}; + use crate::colored::{mark_missing, mark_unexpected}; use crate::expectations::{StringMatches, not, string_matches}; use crate::spec::{ - DiffFormat, Expectation, Expecting, Expression, FailingStrategy, Invertible, Spec, + DiffFormat, DisplayRepresentation, Expectation, Expecting, Expression, FailingStrategy, + Invertible, Represent, Spec, }; - use crate::std::fmt::Debug; use crate::std::format; use crate::std::string::String; - impl AssertStringMatches for Spec<'_, S, R> + impl AssertStringMatches for Spec<'_, S, D, R> where - S: AsRef + Debug, + S: AsRef, + D: Represent, R: FailingStrategy, { fn matches(self, regex_pattern: &str) -> Self { @@ -726,9 +788,9 @@ mod regex { } } - impl Expectation for StringMatches<'_> + impl Expectation for StringMatches<'_> where - S: AsRef + Debug, + S: AsRef, { fn test(&mut self, subject: &S) -> bool { self.regex.is_match(subject.as_ref()) @@ -739,6 +801,7 @@ mod regex { expression: &Expression<'_>, actual: &S, inverted: bool, + _representation: &D, format: &DiffFormat, ) -> String { let (not, does_not_match) = if inverted { @@ -747,8 +810,8 @@ mod regex { ("", "does not match") }; let regex = self.regex.as_str(); - let marked_actual = mark_unexpected_string(actual.as_ref(), format); - let marked_expected = mark_missing_string(regex, format); + let marked_actual = mark_unexpected(actual.as_ref(), &DisplayRepresentation, format); + let marked_expected = mark_missing(regex, &DisplayRepresentation, format); format!( "expected {expression} to {not}match the regex {regex}\n but was: {marked_actual}\n {does_not_match} regex: {marked_expected}" ) diff --git a/src/string/tests.rs b/src/string/tests.rs index ac3f31e..b86af21 100644 --- a/src/string/tests.rs +++ b/src/string/tests.rs @@ -1151,14 +1151,14 @@ fn verify_string_ends_with_string_fails() { let failures = verify_that(subject) .named("my_thing") - .ends_with("abrupt end".to_string()) + .ends_with("erit abrupt end".to_string()) .display_failures(); assert_eq!( failures, - &[r#"expected my_thing to end with "abrupt end" + &[r#"expected my_thing to end with "erit abrupt end" but was: "possim deserunt obcaecat hendrerit" - expected: "abrupt end" + expected: "erit abrupt end" "#] ); }