proptest 1.4.0

Hypothesis-like property-based testing and shrinking.
Documentation
//-
// Copyright 2017 Jason Lingle
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use crate::std_facade::{fmt, Arc, Cell};

use crate::strategy::traits::*;
use crate::test_runner::*;

/// `Strategy` and `ValueTree` filter_map adaptor.
///
/// See `Strategy::prop_filter_map()`.
#[must_use = "strategies do nothing unless used"]
pub struct FilterMap<S, F> {
    pub(super) source: S,
    pub(super) whence: Reason,
    pub(super) fun: Arc<F>,
}

impl<S, F> FilterMap<S, F> {
    pub(super) fn new(source: S, whence: Reason, fun: F) -> Self {
        Self {
            source,
            whence,
            fun: Arc::new(fun),
        }
    }
}

impl<S: fmt::Debug, F> fmt::Debug for FilterMap<S, F> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("FilterMap")
            .field("source", &self.source)
            .field("whence", &self.whence)
            .field("fun", &"<function>")
            .finish()
    }
}

impl<S: Clone, F> Clone for FilterMap<S, F> {
    fn clone(&self) -> Self {
        Self {
            source: self.source.clone(),
            whence: self.whence.clone(),
            fun: Arc::clone(&self.fun),
        }
    }
}

impl<S: Strategy, F: Fn(S::Value) -> Option<O>, O: fmt::Debug> Strategy
    for FilterMap<S, F>
{
    type Tree = FilterMapValueTree<S::Tree, F, O>;
    type Value = O;

    fn new_tree(&self, runner: &mut TestRunner) -> NewTree<Self> {
        loop {
            let val = self.source.new_tree(runner)?;
            if let Some(current) = (self.fun)(val.current()) {
                return Ok(FilterMapValueTree::new(val, &self.fun, current));
            } else {
                runner.reject_local(self.whence.clone())?;
            }
        }
    }
}

/// `ValueTree` corresponding to `FilterMap`.
pub struct FilterMapValueTree<V, F, O> {
    source: V,
    current: Cell<Option<O>>,
    fun: Arc<F>,
}

impl<V: Clone + ValueTree, F: Fn(V::Value) -> Option<O>, O> Clone
    for FilterMapValueTree<V, F, O>
{
    fn clone(&self) -> Self {
        Self::new(self.source.clone(), &self.fun, self.fresh_current())
    }
}

impl<V: fmt::Debug, F, O> fmt::Debug for FilterMapValueTree<V, F, O> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("FilterMapValueTree")
            .field("source", &self.source)
            .field("current", &"<current>")
            .field("fun", &"<function>")
            .finish()
    }
}

impl<V: ValueTree, F: Fn(V::Value) -> Option<O>, O>
    FilterMapValueTree<V, F, O>
{
    fn new(source: V, fun: &Arc<F>, current: O) -> Self {
        Self {
            source,
            current: Cell::new(Some(current)),
            fun: Arc::clone(fun),
        }
    }

    fn fresh_current(&self) -> O {
        (self.fun)(self.source.current())
            .expect("internal logic error; this is a bug!")
    }

    fn ensure_acceptable(&mut self) {
        loop {
            if let Some(current) = (self.fun)(self.source.current()) {
                // Found an acceptable element!
                self.current = Cell::new(Some(current));
                break;
            } else if !self.source.complicate() {
                panic!(
                    "Unable to complicate filtered strategy \
                     back into acceptable value"
                );
            }
        }
    }
}

impl<V: ValueTree, F: Fn(V::Value) -> Option<O>, O: fmt::Debug> ValueTree
    for FilterMapValueTree<V, F, O>
{
    type Value = O;

    fn current(&self) -> O {
        // Optimization: we avoid the else branch in most success cases
        // thereby avoiding to call the closure and the source tree.
        if let Some(current) = self.current.replace(None) {
            current
        } else {
            self.fresh_current()
        }
    }

    fn simplify(&mut self) -> bool {
        if self.source.simplify() {
            self.ensure_acceptable();
            true
        } else {
            false
        }
    }

    fn complicate(&mut self) -> bool {
        if self.source.complicate() {
            self.ensure_acceptable();
            true
        } else {
            false
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_filter_map() {
        let input = (0..256).prop_filter_map("%3 + 1", |v| {
            if 0 == v % 3 {
                Some(v + 1)
            } else {
                None
            }
        });

        for _ in 0..256 {
            let mut runner = TestRunner::default();
            let mut case = input.new_tree(&mut runner).unwrap();

            assert_eq!(0, (case.current() - 1) % 3);

            while case.simplify() {
                assert_eq!(0, (case.current() - 1) % 3);
            }
            assert_eq!(0, (case.current() - 1) % 3);
        }
    }

    #[test]
    fn test_filter_map_sanity() {
        check_strategy_sanity(
            (0..256).prop_filter_map("!%5 * 2", |v| {
                if 0 != v % 5 {
                    Some(v * 2)
                } else {
                    None
                }
            }),
            Some(CheckStrategySanityOptions {
                // Due to internal rejection sampling, `simplify()` can
                // converge back to what `complicate()` would do.
                strict_complicate_after_simplify: false,
                ..CheckStrategySanityOptions::default()
            }),
        );
    }
}