use exonum::storage::Snapshot;
#[derive(Debug)]
pub struct Comparison<T> {
old: T,
new: T,
}
impl<T> Comparison<T> {
pub fn new(old: T, new: T) -> Self {
Comparison { old, new }
}
pub fn map<'a, F, U>(&'a self, f: F) -> Comparison<U>
where
F: Fn(&'a T) -> U,
{
Comparison::new(f(&self.old), f(&self.new))
}
pub fn assert_before<P>(&self, message: &str, predicate: P) -> &Self
where
P: Fn(&T) -> bool,
{
assert!(
predicate(&self.old),
format!("Precondition does not hold: {}", message)
);
self
}
pub fn assert_after<P>(&self, message: &str, predicate: P) -> &Self
where
P: Fn(&T) -> bool,
{
assert!(
predicate(&self.new),
format!("Postcondition does not hold: {}", message)
);
self
}
pub fn assert<P>(&self, message: &str, predicate: P) -> &Self
where
P: Fn(&T, &T) -> bool,
{
assert!(
predicate(&self.old, &self.new),
format!("Comparison does not hold: {}", message)
);
self
}
pub fn assert_inv<P>(&self, message: &str, predicate: P) -> &Self
where
P: Fn(&T) -> bool,
{
assert!(
predicate(&self.old),
format!("Invariant does not hold for the older state: {}", message)
);
assert!(
predicate(&self.new),
format!("Invariant does not hold for the newer state: {}", message)
);
self
}
}
impl<T: PartialEq + ::std::fmt::Debug> Comparison<T> {
pub fn assert_eq(&self, message: &str) -> &Self {
assert_eq!(self.old, self.new, "Invariant does not hold: {}", message);
self
}
pub fn assert_ne(&self, message: &str) -> &Self {
assert_ne!(self.old, self.new, "Expected change: {}", message);
self
}
}
pub trait ComparableSnapshot<S> {
fn compare(self, old: S) -> Comparison<Box<dyn Snapshot>>;
}
impl ComparableSnapshot<Box<dyn Snapshot>> for Box<dyn Snapshot> {
fn compare(self, old: Box<dyn Snapshot>) -> Comparison<Box<dyn Snapshot>> {
Comparison::new(old, self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_assertions() {
let comp = Comparison::new(vec![1, 2, 3], vec![4, 5, 6]);
comp.assert_before("Array should have length 3", |old| old.len() == 3)
.assert_after("Array should have length 3", |new| new.len() == 3)
.assert("Array should be transformed", |old, new| {
new.iter().enumerate().all(|(i, &x)| x == old[i] + 3)
})
.map(Vec::len)
.assert("Lengths should be the same", |old, new| old == new);
}
#[test]
#[should_panic(expected = "Precondition does not hold: Array should have length 3")]
fn test_assertion_precondition_failure() {
let comp = Comparison::new(vec![1, 2], vec![4, 5, 6]);
comp.assert_before("Array should have length 3", |old| old.len() == 3);
}
#[test]
#[should_panic(expected = "Postcondition does not hold: Array should have length 3")]
fn test_assertion_postcondition_failure() {
let comp = Comparison::new(vec![1, 2, 3], vec![4, 5]);
comp.assert_after("Array should have length 3", |new| new.len() == 3);
}
#[test]
#[should_panic(expected = "Comparison does not hold: Array should be transformed")]
fn test_assertion_transform_failure() {
let comp = Comparison::new(vec![1, 2, 3], vec![4, 5, 7]);
comp.assert("Array should be transformed", |old, new| {
new.iter().enumerate().all(|(i, &x)| x == old[i] + 3)
});
}
#[test]
#[should_panic(expected = "Array length more than 1")]
fn test_assertion_invariant_failure_pre() {
let comp = Comparison::new(vec![1], vec![2, 3, 4]);
comp.assert_inv("Array length more than 1", |v| v.len() > 1);
}
#[test]
#[should_panic(expected = "Array length more than 1")]
fn test_assertion_invariant_failure_post() {
let comp = Comparison::new(vec![1, 2, 3], vec![4]);
comp.assert_inv("Array length more than 1", |v| v.len() > 1);
}
}