diff --git a/README.md b/README.md index ddf5888..1d6bffb 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,80 @@ steps: expected: "0.png" ``` -Supports colored messages with the `colors` feature. +Supports: + +- colored messages with the `colors` feature +- diffing with the `diff` feature +- async assertions with the `futures` feature +- regular expressions with the `regex` feature + +### Output diffs + +Check how outputs differ based on their `Display` representations where +available: + +```text +---- string_diff stdout ---- +thread 'string_diff' panicked at tests\examples.rs:8:5: +assertion failed: + at: tests\examples.rs:8:5 [examples] + subject: "The quick\nbrown fox\njumped over\nthe lazy\ndog." + +steps: + to_equal: [1] values not equal + received: "The quick\nbrown fox\njumped over\nthe lazy\ndog." + expected: "the quick brown\nspotted fox\njumped over\nthe lazyish\ndog." + +----- diff [1] ----- +- the quick brown ++ The quick +- spotted fox ++ brown fox + jumped over +- the lazyish ++ the lazy + dog. +``` + +Or, for types that only implement `Debug`, use that representation automatically +instead: + +```text +---- debug_diff stdout ---- +thread 'debug_diff' panicked at tests\examples.rs:28:5: +assertion failed: + at: tests\examples.rs:28:5 [examples] + subject: [A { inner: 1 }, A { inner: 3 }, A { inner: 3 }, A { inner: 512 }, A { inner: 761 }] + +steps: + to_equal: [1] values not equal + received: [A { inner: 1 }, A { inner: 3 }, A { inner: 3 }, A { inner: 512 }, A { inner: 761 }] + expected: [A { inner: 1 }, A { inner: 2 }, A { inner: 3 }, A { inner: 513 }, A { inner: 761 }] + +----- diff [1] ----- + [ + A { + inner: 1, + }, + A { +- inner: 2, ++ inner: 3, + }, + A { + inner: 3, + }, + A { +- inner: 513, ++ inner: 512, + }, + A { + inner: 761, + }, + ] +``` + +For colored output, try running some of the example tests in the `tests/` +directory. ## Built-in assertions diff --git a/src/macros.rs b/src/macros.rs index 346dc4f..8285bb1 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -91,46 +91,47 @@ /// annotated. Values passed into assertions (and from modifiers to other /// assertions) are *transparently* annotated. /// -/// An annotated value is a value with an additional string representation -/// attached to it. This string representation is generated either from the -/// value's [`Debug`] representation or from the [stringified] source code -/// itself (if no [`Debug`] implementation is available). +/// An annotated value is a value with additional information about its +/// representations. The source code being annotated is [stringified], and +/// whether the type supports [`Debug`] and/or [`Display`] is also captured in a +/// way where those implementations can be used to format the value. /// /// Above, it was noted that applying, for example, the [`not`] modifier to an /// assertion `a` was *functionally* equivalent to calling `not(a)`. In /// implementation, [`not`] does not actually receive the assertion `a`, but /// instead receives a special annotated assertion which wraps `a`. /// -/// This annotated assertion is a hidden modifier that annotates the value that -/// it receives. This means that when calling `expect!(1, not, to_equal(2))`, -/// the value being sent from [`not`] to [`to_equal`] is automatically annotated -/// by this macro. Additionally, the `2` parameter to [`to_equal`] is -/// automatically annotated by this macro, so the [`to_equal`] function is -/// actually not receiving an [`i32`], but an annotated version of it. +/// This annotated assertion is created by a hidden modifier that annotates the +/// value that it receives. This means that when calling +/// `expect!(1, not, to_equal(2))`, the value being sent from [`not`] to +/// [`to_equal`] is automatically annotated by this macro. Additionally, the `2` +/// parameter to [`to_equal`] is automatically annotated by this macro, so the +/// [`to_equal`] function is actually not receiving an [`i32`], but an annotated +/// version of it. /// /// In other words, if the hidden modifier's name is `annotate` and there /// existed a constructor `Annotated(T)` to construct an annotated value, then /// the assertion being called could be simplistically represented as /// `annotate(not(annotate(to_equal(Annotated(2)))))`. Note that the parameter -/// to [`to_equal`] is also annotated, as would any parameters to any modifiers -/// in the chain (if there existed any which accepted parameters). +/// to [`to_equal`] is also annotated, as would be any parameters to any +/// modifiers in the chain (if there existed any which accepted parameters). /// /// This macro must perform the annotation itself to avoid adding additional /// bounds to assertions. This is because this macro performs autoref /// specialization to extract the string representation of the value. Without /// this, the [`to_equal`] assertion would need to have an additional [`Debug`] /// constraint on the values that it receives to be able to display those values -/// in case of an assertion failure, meaning that assertion would not be as -/// useful for values that do not have a [`Debug`] representation. +/// in case of an assertion failure for example, meaning that assertion would +/// not be as useful for values that do not have a [`Debug`] representation. /// /// One limitation of this approach is that values being passed from modifiers /// to other assertions down the chain do not have a meaningful source -/// representation. If those values do not have a [`Debug`] implementation, then -/// the string representation of those values will not be meaningful. However, -/// assertions can see whether a meaningful string representation is available -/// before generating error messages, and this approach removes the burden on -/// assertions (and users) to constrain their inputs to values that can be -/// meaningfully represented as a string. +/// representation. If those values do not have a [`Debug`] or [`Display`] +/// implementation, then the string representation of those values will not be +/// meaningful. However, assertions can see whether a meaningful string +/// representation is available before generating error messages, and this +/// approach removes the burden on assertions (and users) to constrain their +/// inputs to values that can be meaningfully represented as a string. /// /// Note that there will not always be a meaningful string representation of a /// value. For values defined directly in source code (like `2` in the example @@ -145,6 +146,7 @@ /// [`Annotated`]: crate::metadata::Annotated /// [`AnnotatedAssertion`]: crate::assertions::AnnotatedAssertion /// [`Debug`]: std::fmt::Debug +/// [`Display`]: std::fmt::Display /// [`all`]: crate::prelude::IteratorAssertions::all /// [`map`]: crate::prelude::GeneralAssertions::map /// [`not`]: crate::prelude::GeneralAssertions::not diff --git a/src/metadata/annotated.rs b/src/metadata/annotated.rs index c4eb608..09490b0 100644 --- a/src/metadata/annotated.rs +++ b/src/metadata/annotated.rs @@ -26,17 +26,11 @@ macro_rules! annotated { }}; } -/// A value annotated with its string representation. +/// A value annotated with information on how to represent it as a string. /// -/// This holds a string representation of the stored value. The string -/// representation is obtained in the following order of precedence: -/// -/// 1. the [`Debug`] representation, otherwise... -/// 2. the [stringified](std::stringify) source code (that was annotated). -/// -/// The stringified source code is always available as well, which can be -/// helpful for providing error messages that refer to the actual source code -/// of a value. +/// This holds a [stringified](std::stringify) representation of the source code +/// that was annotated as well as functions to extract [`Debug`] and [`Display`] +/// representations of the value, if the annotated value supports it. /// /// One drawback is that if the annotated value was a variable, the source /// representation is the name of that variable, which may provide limited @@ -45,7 +39,7 @@ macro_rules! annotated { /// generated by the [`expect!`](crate::expect!) macro (which annotates /// intermediate values inside of closures). In this case, the only way to /// generate a meaningful string representation of the value is for that value -/// to implement [`Debug`]. +/// to implement [`Debug`] or [`Display`]. /// /// This type makes no guarantees about the string representation of the /// contained value except for where the representation comes from. Two @@ -114,17 +108,6 @@ impl Annotated { pub fn as_display(&self) -> Option<&dyn Display> { self.as_display.map(|f| f(&self.value)) } - - /// Gets whether this value has a representation other than the stringified - /// source code representation. - /// - /// The stringified source code is not always useful (since it can be an - /// intermediate variable name, for example), so sometimes it's helpful to - /// know if a known useful representation of this value exists. - #[inline] - pub fn has_non_stringified_repr(&self) -> bool { - self.as_debug.is_some() || self.as_display.is_some() - } } impl Annotated diff --git a/tests/examples.rs b/tests/examples.rs new file mode 100644 index 0000000..e1ac2bf --- /dev/null +++ b/tests/examples.rs @@ -0,0 +1,29 @@ +use expecters::prelude::*; + +#[test] +#[ignore = "run this test manually to see the output"] +fn string_diff() { + let left = "The quick\nbrown fox\njumped over\nthe lazy\ndog."; + let right = "the quick brown\nspotted fox\njumped over\nthe lazyish\ndog."; + expect!(left, to_equal(right)); +} + +#[test] +#[ignore = "run this test manually to see the output"] +fn debug_diff() { + #[derive(PartialEq, Eq, Debug)] + struct A { + inner: i32, + } + + impl A { + pub fn new(inner: i32) -> Self { + Self { inner } + } + } + + let subject = vec![A::new(1), A::new(3), A::new(3), A::new(512), A::new(761)]; + let expected = vec![A::new(1), A::new(2), A::new(3), A::new(513), A::new(761)]; + + expect!(subject, to_equal(expected)); +}