From a99cd9e670628e45915ba2b17fa4e3e6b3258581 Mon Sep 17 00:00:00 2001 From: Julian Ganz Date: Thu, 18 Mar 2021 13:17:34 +0100 Subject: [PATCH] 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 | 2 +- src/tester.rs | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 58dc99d..5938081 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,7 +15,7 @@ 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, QuickCheck, TestResult, Equivalence, Testable}; /// A macro for writing quickcheck tests. /// diff --git a/src/tester.rs b/src/tester.rs index e2eaa20..d03d447 100644 --- a/src/tester.rs +++ b/src/tester.rs @@ -281,6 +281,47 @@ impl 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. /// @@ -314,6 +355,18 @@ 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,