1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
//! Hints help to analyze a single test run, mostly the counterexample.
//!
//! You can put context information like local variables
//! inside of hints. Use it to reveal what test data were generated or
//! which branches were taken. Hints must be enabled with the feature
//! `hints`.

#[cfg(feature = "hints")]
use crate::util::events;

/// A single hint that contains context information.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Hint {
    /// The indent level of the text.
    pub indent: usize,
    /// Contains the context information.
    pub text: String,
}

/// A collection of hints.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Hints(pub Vec<Hint>);

impl Hints {
    /// Returns an instance without any hints.
    pub fn new() -> Self {
        Hints(Vec::new())
    }
}

#[cfg(feature = "hints")]
struct InterimHints {
    current_indent: usize,
    hints: Hints,
}

#[cfg(feature = "hints")]
impl events::Events for InterimHints {
    fn new() -> Self {
        InterimHints {
            current_indent: 0,
            hints: Hints::new(),
        }
    }

    fn take(&mut self) -> Self {
        InterimHints {
            current_indent: self.current_indent,
            hints: Hints(self.hints.0.drain(..).collect()),
        }
    }
}

#[cfg(feature = "hints")]
thread_local! {
    static LOCAL: events::Stack<InterimHints> = events::new_stack();
}

/// Returns all hints that were added during the evaluation of the given function.
pub fn collect<R>(f: impl FnOnce() -> R) -> (R, Hints) {
    #[cfg(feature = "hints")]
    {
        let (result, interim) = events::collect(&LOCAL, f);
        (result, interim.hints)
    }
    #[cfg(not(feature = "hints"))]
    {
        (f(), Hints::new())
    }
}

/// Returns if hints are currently enabled.
///
/// Hints are enabled if and only if this function is executed inside of `collect` and
/// the feature `hints` is present.
pub fn enabled() -> bool {
    #[cfg(feature = "hints")]
    {
        events::enabled(&LOCAL)
    }
    #[cfg(not(feature = "hints"))]
    {
        false
    }
}

/// If hints are enabled, this function evaluates and adds the given hint. Otherwise this function
/// is a noop.
pub fn add(message_text: impl FnOnce() -> String) {
    #[cfg(feature = "hints")]
    {
        events::modify(&LOCAL, move |stack| {
            let text = message_text();
            let len = stack.len();

            fn add_message(interim: &mut InterimHints, text: String) {
                let indent = interim.current_indent;
                let message = Hint { indent, text };
                interim.hints.0.push(message);
            }

            stack[0..len - 1]
                .iter_mut()
                .for_each(|collection| add_message(collection, text.clone()));
            add_message(&mut stack[len - 1], text);
        });
    }
    #[cfg(not(feature = "hints"))]
    {
        drop(message_text);
    }
}

/// Increases the indent of all following added hints.
pub fn indent() {
    #[cfg(feature = "hints")]
    {
        events::modify(&LOCAL, |stack| {
            stack.iter_mut().for_each(|interim| {
                let current_indent = interim.current_indent;
                interim.current_indent = current_indent.saturating_add(1);
            });
        });
    }
}

/// Decreases the indent of all following added hints.
pub fn unindent() {
    #[cfg(feature = "hints")]
    {
        events::modify(&LOCAL, |stack| {
            stack.iter_mut().for_each(|interim| {
                let current_indent = interim.current_indent;
                interim.current_indent = current_indent.saturating_sub(1);
            });
        });
    }
}