-
Notifications
You must be signed in to change notification settings - Fork 159
Description
Some classes of properties checked with this crate may merit some convenience functionality. One of those classes being properties which essentially check whether some transformation is equivalent to identity.
One domain in which I found this approach useful are parsers: you'd format some arbitrary data-structure and read it back through the parser, but I suspect you end up constructing pseudo-identities in quite a few domains. In fact, the very example in the readme tests for such a property:
#[quickcheck]
fn double_reversal_is_identity(xs: Vec<isize>) -> bool {
xs == reverse(&reverse(&xs))
}Depending on the complexity of the functions involved in the construction, tracking the input reported by quickcheck through the code may be rather tedious, even after the inputs are shrunk. Knowing how exactly the result of the pseudo-identity differs from its input can help narrowing down the error. So users may end up printing both as part of the test:
#[quickcheck]
fn double_reversal_is_identity(xs: Vec<isize>) -> bool {
let res = reverse(&reverse(&xs));
if xs == res {
true
} else {
eprintln!("Original: {:?}", xs);
eprintln!("Identity: {:?}", xs);
false
}
}However, this also results in them being printed during the shrinking, which is not ideal. I assume this wouldn't be the case if using TestResult::error instead of eprintln, and we can always construct the test ourselves. But still, we end up writing common boilerplate for that class of tests. It would be nice if we could write the following, instead:
#[quickcheck(identity)]
fn double_reversal_is_identity(xs: Vec<isize>) -> Vec<isize> {
reverse(&reverse(&xs))
}Quickcheck would generate a test which does the comparison between the function's input and output. If they differ, it would also print both input and output after shrinking.
A generalization of this class would be cases in which we want to compare two values. We'd write:
#[quickcheck(equivalence)]
fn double_reversal_is_identity(xs: Vec<isize>) -> (Vec<isize>, Vec<isize>) {
(reverse(&reverse(&xs)), xs)
}And the macro would generate a test which compares the two values in the tuple and prints them (along with the input) after shrinking. In this case, the output (i.e. the tuple element) would not need to be of the same type as the inputs.
This would obviously be a convenience feature. I volunteer implementing it, but I'd like to hear opinions on whether this is actually something anyone else would find useful first.