From dd7a4a06c67214c8897c63c61ed67ab62491b026 Mon Sep 17 00:00:00 2001 From: Julian Ganz Date: Thu, 18 Mar 2021 13:17:34 +0100 Subject: [PATCH 1/3] Add a convenience type for properties involving equivalence This type was originally motivated by observing that a class of properties will involve a pseudo-identity followed by a check for equivalence between its input and output. A generalization of would be properties which are defined by the equivalence check, only. For this class, we usually want to know _how_ those two values differ, rather than only that they do. Test-authors may thus write tests like the following in order to include those values in a failure report: fn revrev(xs: Vec) -> TestResult { let rev: Vec<_> = xs.clone().into_iter().rev().collect(); let revrev: Vec<_> = rev.into_iter().rev().collect(); if xs == revrev { TestResult::passed() } else { TestResult::error( format!("Original: '{:?}', Identity: '{:?}'", xs, revrev) ) } } This change introduces a convenience type which encapsulates the equivalence check as well as the error message generation. Using it, the above test could be written as: fn revrev(xs: Vec) -> Equivalence> { let rev: Vec<_> = xs.clone().into_iter().rev().collect(); let revrev: Vec<_> = rev.into_iter().rev().collect(); Equivalence::of(xs, revrev) } --- src/lib.rs | 4 +++- src/tester.rs | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index d3a5ba4..5e93d69 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,7 +20,9 @@ semver compatible releases. */ pub use crate::arbitrary::{empty_shrinker, single_shrinker, Arbitrary, Gen}; -pub use crate::tester::{quickcheck, QuickCheck, TestResult, Testable}; +pub use crate::tester::{ + quickcheck, Equivalence, QuickCheck, TestResult, Testable, +}; /// A macro for writing quickcheck tests. /// diff --git a/src/tester.rs b/src/tester.rs index f09326c..b41dfb5 100644 --- a/src/tester.rs +++ b/src/tester.rs @@ -306,6 +306,49 @@ impl From for TestResult { } } +/// Utility type for properties involving an equivalence +/// +/// Sometimes, properties we want to test for are the equivalence of two values. +/// For example, we may construct a pseudeo-identity from a formatter and a +/// parser in order to test a parser. In such cases, we want to compare the +/// input of the pseudo-identity to its output. +/// +/// `Equivalence` is a `Testable` type which expresses this intent, but also +/// includes both values as part of the failure report if a test fails. +/// +/// # Example +/// +/// ```rust +/// use quickcheck::{QuickCheck, Equivalence}; +/// +/// fn prop_reverse_reverse() { +/// fn revrev(xs: Vec) -> Equivalence> { +/// let rev: Vec<_> = xs.clone().into_iter().rev().collect(); +/// let revrev: Vec<_> = rev.into_iter().rev().collect(); +/// Equivalence::of(xs, revrev) +/// } +/// QuickCheck::new().quickcheck(revrev as fn(Vec) -> Equivalence>); +/// } +/// ``` +#[derive(Clone, Debug)] +pub struct Equivalence(T, T) +where + T: Debug + PartialEq + 'static; + +impl Equivalence +where + T: Debug + PartialEq + 'static, +{ + /// Construct a value expressing the equivalence of the given values + /// + /// In many cases, you'll be able to construct an instance for two values + /// `a` and `b` via `Equivalence(a, b)`. This function is intended for + /// situations where you can't for whatever reasons. + pub fn of(left: T, right: T) -> Self { + Self(left, right) + } +} + /// `Testable` describes types (e.g., a function) whose values can be /// tested. /// @@ -339,6 +382,22 @@ impl Testable for TestResult { } } +impl Testable for Equivalence +where + T: Debug + PartialEq + 'static, +{ + fn result(&self, _: &mut Gen) -> TestResult { + if self.0 == self.1 { + TestResult::passed() + } else { + TestResult::error(format!( + "Missmatch! Left: '{:?}', Right: '{:?}'", + self.0, self.1 + )) + } + } +} + impl Testable for Result where A: Testable, From 25d155f970e91396a333a29000698adef9b3daa0 Mon Sep 17 00:00:00 2001 From: Julian Ganz Date: Fri, 26 Mar 2021 19:39:49 +0100 Subject: [PATCH 2/3] Make fields in Equivalence public `Equivalence` is meant to be trivially constructible, which was the movitation behind making it a tuple struct. However, it's still a _struct_ and its fields are not pubilic by default. Therefore, the type was not constructible as advertised, e.g. via `Equivalence(a, b)`. This change makes these fields public, as they are supposed to be. --- src/tester.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tester.rs b/src/tester.rs index b41dfb5..606da4f 100644 --- a/src/tester.rs +++ b/src/tester.rs @@ -331,7 +331,7 @@ impl From for TestResult { /// } /// ``` #[derive(Clone, Debug)] -pub struct Equivalence(T, T) +pub struct Equivalence(pub T, pub T) where T: Debug + PartialEq + 'static; From da6b9e9b006910f53753ec41525c2b5456d6ede8 Mon Sep 17 00:00:00 2001 From: Julian Ganz Date: Sat, 3 Jul 2021 10:31:03 +0200 Subject: [PATCH 3/3] Add a function to Equivalence for convenient conversion to a TestResult `quickcheck` allows to skip tests (for given inputs) by returning a `TestResult` indicating that it should be ignored. Naturally, an `Equivalence` cannot express such an intent and neither can any other `Testable`s other than functions and `TestResult` itself. Hence, if we need the ability to skip tests, we need to return a `TestResult` or some dependent type (e.g. `Result`) from our property function. If we still want to make use of `Equivalence` in such tests, we need to convert it to a `TestResult`. Previously, we had to resort to using `Testable::result` with some dummy `&mut Gen`, even though it isn't even used in the conversion. As a remedy, this change moves the conversion into a dedicates function (with no additional parameters) which may be called inside property functions. --- src/tester.rs | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/tester.rs b/src/tester.rs index 606da4f..91145b0 100644 --- a/src/tester.rs +++ b/src/tester.rs @@ -347,6 +347,23 @@ where pub fn of(left: T, right: T) -> Self { Self(left, right) } + + /// Create a `TestResult` reflecting this equivalence + /// + /// If the two values are equal, the returned `TestResult` will indicate + /// success, otherwise it will indicate failure and include a message + /// reflecting both values. + pub fn as_result(&self) -> TestResult { + let Self(l, r) = self; + if l == r { + TestResult::passed() + } else { + TestResult::error(format!( + "Missmatch! Left: '{:?}', Right: '{:?}'", + l, r + )) + } + } } /// `Testable` describes types (e.g., a function) whose values can be @@ -387,14 +404,7 @@ where T: Debug + PartialEq + 'static, { fn result(&self, _: &mut Gen) -> TestResult { - if self.0 == self.1 { - TestResult::passed() - } else { - TestResult::error(format!( - "Missmatch! Left: '{:?}', Right: '{:?}'", - self.0, self.1 - )) - } + self.as_result() } }