blob: 37aa3a93691483b4391eb94c973c5e4e82fc7865 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
pretty::{BoxAllocator, DocAllocator},
std::{
fmt::{self, Debug, Display, Formatter},
sync::Arc,
},
};
/// Asynchronous extensions to Expectation Predicates
pub mod asynchronous;
/// Expectations for the host driver
pub mod host_driver;
/// Expectations for remote peers
pub mod peer;
/// Useful convenience methods and macros for working with expectations
#[macro_use]
pub mod prelude;
/// Tests for the expectation module
#[cfg(test)]
pub mod test;
/// A String whose `Debug` implementation pretty-prints. Used when we need to return a string which
/// we know will be printed through it's 'Debug' implementation, but we want to ensure that it is
/// not further escaped (for example, because it already is escaped, or because it contains
/// characters that we do not want to be escaped).
///
/// Essentially, formatting a DebugString with '{:?}' formats as if it was '{}'
#[derive(Clone, PartialEq)]
pub struct DebugString(String);
// The default formatting width for pretty printing output
const FMT_CHAR_WIDTH: usize = 100;
impl fmt::Debug for DebugString {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
/// Simplified `DocBuilder` alias used in this file
type DocBuilder<'a> = pretty::DocBuilder<'a, BoxAllocator>;
fn parens<'a>(alloc: &'a BoxAllocator, doc: DocBuilder<'a>) -> DocBuilder<'a> {
alloc.text("(").append(doc).append(alloc.text(")"))
}
/// Functionalized Standard Debug Formatting
fn show_debug<T: Debug>(t: &T) -> String {
format!("{:?}", t)
}
/// Function to compare two `T`s
type Comparison<T> = Arc<dyn Fn(&T, &T) -> bool + Send + Sync + 'static>;
/// Function to show a representation of a given `T`
type Show<T> = Arc<dyn Fn(&T) -> String + Send + Sync + 'static>;
/// A Boolean predicate on type `T`. Predicate functions are a boolean algebra
/// just as raw boolean values are; they an be ANDed, ORed, NOTed. This allows
/// a clear and concise language for declaring test expectations.
pub enum Predicate<T> {
Equal(Arc<T>, Comparison<T>, Show<T>),
And(Box<Predicate<T>>, Box<Predicate<T>>),
Or(Box<Predicate<T>>, Box<Predicate<T>>),
Not(Box<Predicate<T>>),
Predicate(Arc<dyn Fn(&T) -> bool + Send + Sync>, String),
// Since we can't use an existential:
// for<U> Over(Predicate<U>, Fn(&T) -> &U)
// we use the trait `IsOver` to hide the type `U` of the intermediary
Over(Arc<dyn IsOver<T> + Send + Sync>),
// Since we can't use an existential:
// for<I> Any(Fn(&T) -> I, Predicate<I::Elem>)
// where I::Elem: Debug
// we use the trait `IsAny` to hide the type `I` of the iterator
Any(Arc<dyn IsAny<T> + Send + Sync + 'static>),
// Since we can't use an existential:
// for<I> All(Fn(&T) -> I, Predicate<I::Elem>)
// where I::Elem: Debug
// we use the trait `IsAll` to hide the type `I` of the iterator
All(Arc<dyn IsAll<T> + Send + Sync + 'static>),
}
// Bespoke clone implementation. This implementation is equivalent to the one that would be derived
// by #[derive(Clone)] _except_ for the trait bounds. The derived impl would automatically require
// `T: Clone`, when actually `Predicate<T>` is `Clone` for _all_ `T`, even those that are not
// `Clone`.
impl<T> Clone for Predicate<T> {
fn clone(&self) -> Self {
match self {
Predicate::Equal(t, comp, repr) => {
Predicate::Equal(t.clone(), comp.clone(), repr.clone())
}
Predicate::And(l, r) => Predicate::And(l.clone(), r.clone()),
Predicate::Or(l, r) => Predicate::Or(l.clone(), r.clone()),
Predicate::Not(x) => Predicate::Not(x.clone()),
Predicate::Predicate(p, msg) => Predicate::Predicate(p.clone(), msg.clone()),
Predicate::Over(x) => Predicate::Over(x.clone()),
Predicate::Any(x) => Predicate::Any(x.clone()),
Predicate::All(x) => Predicate::All(x.clone()),
}
}
}
impl<T> Debug for Predicate<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.describe(&BoxAllocator).1.pretty(FMT_CHAR_WIDTH).fmt(f)
}
}
/// At most how many elements of an iterator to show in a falsification, when falsifying `any` or
/// `all`
const MAX_ITER_FALSIFICATIONS: usize = 5;
fn falsify_elem<'p, 't, 'd, T: Debug>(
pred: &'p Predicate<T>,
index: usize,
el: &'t T,
doc: &'d BoxAllocator,
) -> Option<DocBuilder<'d>> {
pred.falsify(el, doc).map(move |falsification| {
doc.text(format!("[{}]", index))
.append(doc.space().append(doc.text(show_debug(el))).nest(2))
.append(doc.space().append(doc.text("BECAUSE")))
.append(doc.space().append(falsification.group()).nest(2))
.append(doc.text(","))
.group()
})
}
fn fmt_falsifications<'d>(
i: impl Iterator<Item = DocBuilder<'d>>,
doc: &'d BoxAllocator,
) -> Option<DocBuilder<'d>> {
i.take(MAX_ITER_FALSIFICATIONS).fold(None, |acc: Option<DocBuilder<'d>>, falsification| {
Some(acc.map_or(doc.nil(), |d| d.append(doc.space())).append(falsification))
})
}
/// Trait representation of a Predicate on members of an existential iterator type
pub trait IsAny<T> {
fn describe<'d>(&self, doc: &'d BoxAllocator) -> DocBuilder<'d>;
fn falsify_any<'d>(&self, t: &T, doc: &'d BoxAllocator) -> Option<DocBuilder<'d>>;
}
impl<T: 'static, Elem: Debug + 'static> IsAny<T> for Predicate<Elem>
where
for<'a> &'a T: IntoIterator<Item = &'a Elem>,
{
fn describe<'d>(&self, doc: &'d BoxAllocator) -> DocBuilder<'d> {
doc.text("ANY").append(doc.space().append(self.describe(doc)).nest(2)).group()
}
fn falsify_any<'d>(&self, t: &T, doc: &'d BoxAllocator) -> Option<DocBuilder<'d>> {
if t.into_iter().any(|e| self.satisfied(e)) {
None
} else {
// All are falsifications, so display all (up to MAX_ITER_FALSIFICATIONS)
fmt_falsifications(
t.into_iter().enumerate().filter_map(|(i, el)| falsify_elem(&self, i, &el, doc)),
doc,
)
.or(Some(doc.text("<empty>")))
}
}
}
/// Trait representation of a Predicate on members of an existential iterator type
pub trait IsAll<T> {
fn describe<'d>(&self, doc: &'d BoxAllocator) -> DocBuilder<'d>;
fn falsify_all<'d>(&self, t: &T, doc: &'d BoxAllocator) -> Option<DocBuilder<'d>>;
}
impl<T: 'static, Elem: Debug + 'static> IsAll<T> for Predicate<Elem>
where
for<'a> &'a T: IntoIterator<Item = &'a Elem>,
{
fn describe<'d>(&self, doc: &'d BoxAllocator) -> DocBuilder<'d> {
doc.text("ALL").append(doc.space().append(self.describe(doc)).nest(2)).group()
}
fn falsify_all<'d>(&self, t: &T, doc: &'d BoxAllocator) -> Option<DocBuilder<'d>> {
fmt_falsifications(
t.into_iter().enumerate().filter_map(|(i, el)| falsify_elem(&self, i, &el, doc)),
doc,
)
}
}
/// A wrapping newtype to differentiate those types which are iterators themselves, from those that
/// implement `IntoIterator`.
struct OverIterator<Elem>(Predicate<Elem>);
impl<'e, Iter, Elem> IsAll<Iter> for OverIterator<Elem>
where
Elem: Debug + 'static,
Iter: Iterator<Item = &'e Elem> + Clone,
{
fn describe<'d>(&self, doc: &'d BoxAllocator) -> DocBuilder<'d> {
doc.text("ALL").append(doc.space().append(self.0.describe(doc)).nest(2)).group()
}
fn falsify_all<'d>(&self, iter: &Iter, doc: &'d BoxAllocator) -> Option<DocBuilder<'d>> {
fmt_falsifications(
iter.clone().enumerate().filter_map(|(i, el)| falsify_elem(&self.0, i, &el, doc)),
doc,
)
}
}
impl<'e, Iter, Elem> IsAny<Iter> for OverIterator<Elem>
where
Elem: Debug + 'static,
Iter: Iterator<Item = &'e Elem> + Clone,
{
fn describe<'d>(&self, doc: &'d BoxAllocator) -> DocBuilder<'d> {
doc.text("ANY").append(doc.space().append(self.0.describe(doc)).nest(2)).group()
}
fn falsify_any<'d>(&self, iter: &Iter, doc: &'d BoxAllocator) -> Option<DocBuilder<'d>> {
if iter.clone().any(|e| self.0.satisfied(e)) {
None
} else {
// All are falsifications, so display all (up to MAX_ITER_FALSIFICATIONS)
fmt_falsifications(
iter.clone().enumerate().filter_map(|(i, el)| falsify_elem(&self.0, i, &el, doc)),
doc,
)
.or(Some(doc.text("<empty>")))
}
}
}
/// A Predicate lifted over a mapping function from `T` to `U`. Such mappings allow converting a
/// predicate from one type - say, a struct field - to an upper type - such as a whole struct -
/// whilst maintaining information about the relationship.
enum OverPred<T, U> {
ByRef(Predicate<U>, Arc<dyn Fn(&T) -> &U + Send + Sync + 'static>, String),
ByValue(Predicate<U>, Arc<dyn Fn(&T) -> U + Send + Sync + 'static>, String),
}
/// Trait representation of OverPred where `U` is existential
pub trait IsOver<T> {
fn describe<'d>(&self, doc: &'d BoxAllocator) -> DocBuilder<'d>;
fn falsify_over<'d>(&self, t: &T, doc: &'d BoxAllocator) -> Option<DocBuilder<'d>>;
}
impl<T, U> IsOver<T> for OverPred<T, U> {
fn describe<'a>(&self, doc: &'a BoxAllocator) -> DocBuilder<'a> {
let (pred, path) = match self {
OverPred::ByRef(pred, _, path) => (pred, path),
OverPred::ByValue(pred, _, path) => (pred, path),
};
doc.as_string(path).append(doc.space()).append(pred.describe(doc)).group()
}
fn falsify_over<'d>(&self, t: &T, doc: &'d BoxAllocator) -> Option<DocBuilder<'d>> {
let (d, path) = match self {
OverPred::ByRef(pred, project, path) => (pred.falsify((project)(t), doc), path),
OverPred::ByValue(pred, project, path) => (pred.falsify(&(project)(t), doc), path),
};
d.map(|falsification| {
doc.as_string(path).append(doc.space().append(falsification).nest(2)).group()
})
}
}
impl<T> Predicate<T> {
fn is_equal(&self) -> bool {
if let Predicate::Equal(_, _, _) = self {
true
} else {
false
}
}
pub fn describe<'a>(&self, doc: &'a BoxAllocator) -> DocBuilder<'a> {
match self {
Predicate::Equal(expected, _, repr) => {
doc.text("==").append(doc.space()).append(doc.text(repr(expected))).group()
}
Predicate::And(left, right) => parens(doc, left.describe(doc))
.append(doc.space())
.append(doc.text("AND"))
.append(doc.space())
.append(parens(doc, right.describe(doc)))
.group(),
Predicate::Or(left, right) => parens(doc, left.describe(doc))
.nest(2)
.append(doc.space())
.append(doc.text("OR"))
.append(doc.space())
.append(parens(doc, right.describe(doc)).nest(2))
.group(),
// Succinct override for NOT-Equal cases
Predicate::Not(inner) if inner.is_equal() => {
if let Predicate::Equal(expected, _, repr) = &**inner {
doc.text("!=").append(doc.space()).append(doc.text(repr(&*expected))).group()
} else {
// This branch is guarded by inner.is_equal()
unreachable!()
}
}
Predicate::Not(inner) => {
doc.text("NOT").append(doc.space().append(inner.describe(doc)).nest(2)).group()
}
Predicate::Predicate(_, desc) => doc.as_string(desc).group(),
Predicate::Over(over) => over.describe(doc),
Predicate::Any(any) => any.describe(doc),
Predicate::All(all) => all.describe(doc),
}
}
/// Provide a minimized falsification of the predicate, if possible
pub fn falsify<'d>(&self, t: &T, doc: &'d BoxAllocator) -> Option<DocBuilder<'d>> {
match self {
Predicate::Equal(expected, are_eq, repr) => {
if are_eq(expected, t) {
None
} else {
Some(
doc.text(repr(t))
.append(doc.space())
.append(doc.text("!="))
.append(doc.space())
.append(doc.text(repr(expected)))
.group(),
)
}
}
Predicate::And(left, right) => match (left.falsify(t, doc), right.falsify(t, doc)) {
(Some(l), Some(r)) => Some(
parens(doc, l)
.append(doc.space())
.append(doc.text("AND"))
.append(doc.space())
.append(parens(doc, r))
.group(),
),
(Some(l), None) => Some(l),
(None, Some(r)) => Some(r),
(None, None) => None,
},
Predicate::Or(left, right) => left
.falsify(t, doc)
.and_then(|l| right.falsify(t, doc).map(|r| parens(doc, l).append(parens(doc, r)))),
Predicate::Not(inner) => match inner.falsify(t, doc) {
Some(_) => None,
None => match &**inner {
Predicate::Equal(expected, _, repr) => Some(
doc.text(repr(t))
.append(doc.space())
.append(doc.text("=="))
.append(doc.space())
.append(doc.text(repr(&expected))),
),
_ => Some(
doc.text("NOT")
.append(doc.space().append(inner.describe(doc)).nest(2))
.group(),
),
},
},
Predicate::Predicate(pred, desc) => {
if pred(t) {
None
} else {
Some(doc.text("NOT").append(doc.space().append(doc.as_string(desc)).nest(2)))
}
}
Predicate::Over(over) => over.falsify_over(t, doc),
Predicate::Any(any) => any.falsify_any(t, doc),
Predicate::All(all) => all.falsify_all(t, doc),
}
}
pub fn satisfied<'t>(&self, t: &'t T) -> bool {
match self {
Predicate::Equal(expected, are_eq, _) => are_eq(t, expected),
Predicate::And(left, right) => left.satisfied(t) && right.satisfied(t),
Predicate::Or(left, right) => left.satisfied(t) || right.satisfied(t),
Predicate::Not(inner) => !inner.satisfied(t),
Predicate::Predicate(pred, _) => pred(t),
Predicate::Over(over) => over.falsify_over(t, &BoxAllocator).is_none(),
Predicate::Any(any) => any.falsify_any(t, &BoxAllocator).is_none(),
Predicate::All(all) => all.falsify_all(t, &BoxAllocator).is_none(),
}
}
pub fn assert_satisfied(&self, t: &T) -> Result<(), DebugString> {
let doc = BoxAllocator;
match self.falsify(t, &doc) {
Some(falsification) => {
let d = doc
.text("FAILED EXPECTATION")
.append(doc.newline().append(self.describe(&doc)).nest(2))
.append(doc.newline().append(doc.text("BECAUSE")))
.append(doc.newline().append(falsification).nest(2));
Err(DebugString(d.1.pretty(FMT_CHAR_WIDTH).to_string()))
}
None => Ok(()),
}
}
pub fn and(self, rhs: Predicate<T>) -> Predicate<T> {
Predicate::And(Box::new(self), Box::new(rhs))
}
pub fn or(self, rhs: Predicate<T>) -> Predicate<T> {
Predicate::Or(Box::new(self), Box::new(rhs))
}
pub fn not(self) -> Predicate<T> {
Predicate::Not(Box::new(self))
}
/// Construct a simple predicate function
pub fn predicate<F, S>(f: F, label: S) -> Predicate<T>
where
F: for<'t> Fn(&'t T) -> bool + Send + Sync + 'static,
S: Into<String>,
{
Predicate::Predicate(Arc::new(f), label.into())
}
}
/// Convenient implementations that rely on `Debug` to provide output on falsification, and
/// `PartialEq` to provide equality. If you wish to create predicates for `?Debug` types, use the
/// `Predicate` or `Equal` constructors directly.
impl<T: PartialEq + Debug + 'static> Predicate<T> {
/// Construct a Predicate that expects two `T`s to be equal
pub fn equal(t: T) -> Predicate<T> {
Predicate::Equal(Arc::new(t), Arc::new(T::eq), Arc::new(show_debug))
}
/// Construct a Predicate that expects two `T`s to be non-equal
pub fn not_equal(t: T) -> Predicate<T> {
Predicate::Equal(Arc::new(t), Arc::new(T::eq), Arc::new(show_debug)).not()
}
}
/// Predicates on iterable types, those convertible into iterators via IntoIterator (e.g. Vec<_>)
impl<Elem, T> Predicate<T>
where
T: Send + Sync + 'static,
for<'a> &'a T: IntoIterator<Item = &'a Elem>,
Elem: Debug + Send + Sync + 'static,
{
/// Construct a predicate that all elements of an iterable type match a given predicate
/// If the iterable is empty, the predicate will succeed
pub fn all(pred: Predicate<Elem>) -> Predicate<T> {
Predicate::All(Arc::new(pred))
}
/// Construct a predicate that at least one element of an iterable type match a given predicate
/// If the iterable is empty, the predicate will fail
pub fn any(pred: Predicate<Elem>) -> Predicate<T> {
Predicate::Any(Arc::new(pred))
}
}
/// Predicates on types which are an `Iterator`
impl<'e, Elem, Iter> Predicate<Iter>
where
Iter: Iterator<Item = &'e Elem> + Clone,
Elem: Debug + Send + Sync + 'static,
{
/// Construct a predicate that all elements of an Iterator match a given predicate
/// If the Iterator is empty, the predicate will succeed
pub fn iter_all(pred: Predicate<Elem>) -> Predicate<Iter> {
Predicate::All(Arc::new(OverIterator(pred)))
}
/// Construct a predicate that at least one element of an Iterator match a given predicate
/// If the iterator is empty, the predicate will fail
pub fn iter_any(pred: Predicate<Elem>) -> Predicate<Iter> {
Predicate::Any(Arc::new(OverIterator(pred)))
}
}
impl<U: Send + Sync + 'static> Predicate<U> {
/// Lift a predicate over a projection from `T -> &U`. This constructs a Predicate<T> from a
/// predicate on some field (or arbitrary projection) of type `U`.
///
/// This allows:
/// * Creating a Predicate on a struct from a predicate on a field (or field of a field) of
/// that struct.
/// * Creating a Predicate on a type from a predicate on some value computable from that type
///
/// Compared to writing an arbitrary function using the `predicate()` method, an
/// `over` projection allows for more minimal falsification and better error reporting in
/// failure cases.
///
/// Use `over` when your projection function returns a reference. If you need to return a value
/// (for example, a temporary whose reference lifetime would not escape the function), use
/// `over_value`.
pub fn over<F, T, P>(self, project: F, path: P) -> Predicate<T>
where
F: Fn(&T) -> &U + Send + Sync + 'static,
P: Into<String>,
T: 'static,
{
Predicate::Over(Arc::new(OverPred::ByRef(self, Arc::new(project), path.into())))
}
/// Lift a predicate over a projection from `T -> U`. This constructs a Predicate<T> from a
/// predicate on some field (or arbitrary projection) of type `U`.
///
/// This allows:
/// * Creating a Predicate on a struct from a predicate on a field (or field of a field) of
/// that struct.
/// * Creating a Predicate on a type from a predicate on some value computable from that type
///
/// Compared to writing an arbitrary function using the `predicate()` method, an
/// `over` projection allows for more minimal falsification and better error reporting in
/// failure cases.
///
/// Use `over_value` when your projection function needs to return a value. If you can return a
/// reference, use `over`.
pub fn over_value<F, T, P>(self, project: F, path: P) -> Predicate<T>
where
F: Fn(&T) -> U + Send + Sync + 'static,
P: Into<String>,
T: 'static,
{
Predicate::Over(Arc::new(OverPred::ByValue(self, Arc::new(project), path.into())))
}
}
/// A macro for allowing more ergonomic projection of predicates 'over' some projection function
/// that focuses on a subset of a data type.
///
/// Specify the name of the parent type, the field to focus on, and then pass in the predicate over
/// that field
///
/// usage: over!(Type:field, predicate)
///
/// e.g.
///
/// ```
/// let predicate = over!(RemoteDevice:name, P::equal(Some("INCORRECT_NAME".to_string())));
/// ```
#[macro_export]
macro_rules! over {
($type:ty : $selector:tt, $pred:expr) => {
$pred.over(|var: &$type| &var.$selector, format!(".{}", stringify!($selector)))
};
}
/// A simple macro to allow idiomatic assertion of predicates, producing well formatted
/// falsifications if the predicate fails.
///
/// usage:
///
/// ```
/// let predicate = correct_name().or(correct_address());
/// assert_satisfies!(&test_peer(), predicate);
/// ```
#[macro_export]
macro_rules! assert_satisfies {
($subject:expr, $pred:expr) => {
if let Err(msg) = $pred.assert_satisfied($subject) {
panic!("{}", msg.0)
}
};
}