test4a/runners/
runner.rs

1//! Defines the type `Runner`.
2
3use crate::messages::MessageType;
4use crate::runners::act::Act;
5use crate::{Assert, Message, PanicWhen};
6use std::panic;
7use std::panic::AssertUnwindSafe;
8
9const NO_PANIC_ERROR: &str = "Code has not panicked";
10const PANIC_ERROR: &str = "Code has panicked";
11
12/// Describes and runs multiple tests.
13///
14/// # Examples
15///
16/// ```rust
17/// use test4a::{Message, Equal, Runner, PanicWhen};
18///
19/// fn add_0(message: &mut Message, value: &mut usize) {
20///     message.set("Add 0");
21///     *value += 0;
22/// }
23///
24/// fn add_1(message: &mut Message, value: &mut usize) {
25///     message.set("Add 1");
26///     *value += 1;
27/// }
28///
29/// fn subtract_1(message: &mut Message, value: &mut usize) {
30///     message.set("Subtract 1");
31///     *value -= 1;
32/// }
33///
34/// fn value_expected(
35///     message: &mut Message,
36///     value: usize,
37///     expected: Expected,
38/// ) -> Equal<usize> {
39///     message.set("Value is expected");
40///     Equal::new(value, expected.value)
41/// }
42///
43/// struct Expected {
44///     value: usize
45/// }
46///
47///
48/// // Test #1
49/// Runner::arrange(|message| {
50///     message.set("Initial value of 1");
51///     1
52/// })
53/// .act(add_0, || Expected { value: 1 })
54/// .act(add_1, || Expected { value: 2 })
55/// .act(subtract_1, || Expected { value: 0 })
56/// .assert(value_expected);
57///
58/// // Test #2
59/// Runner::arrange(|message| {
60///     message.set("Initial value of 42");
61///     42
62/// })
63/// .act(add_0, || Expected { value: 42 })
64/// .act(add_1, || Expected { value: 43 })
65/// .act(subtract_1, || Expected { value: 41 })
66/// .assert(value_expected);
67///
68/// // ...
69/// ```
70pub struct Runner<'a, T, E> {
71    arrange_function: Box<Fn(&mut Message) -> T + 'a>,
72    actions: Vec<Act<'a, T, E>>,
73}
74
75impl<'a, T, E> Runner<'a, T, E> {
76    /// Constructor.
77    ///
78    /// `function` defines how the value to test should be initialized.<br>
79    /// It isn't called immediately, but only for each defined test.
80    pub fn arrange(function: impl Fn(&mut Message) -> T + 'a) -> Self {
81        Self {
82            arrange_function: Box::new(function),
83            actions: Vec::new(),
84        }
85    }
86
87    /// Define an action to execute after the value to test has been created.
88    ///
89    /// The Arrange step is executed before each defined Act step to have
90    /// independent tests.
91    pub fn act(
92        &mut self,
93        action: impl Fn(&mut Message, &mut T) + 'a,
94        expectation: impl Fn() -> E + 'a,
95    ) -> &mut Self {
96        self.actions.push(Act::new(action, expectation));
97        self
98    }
99
100    /// Define an action that should panic.
101    ///
102    /// The check is done immediately on method call.<br>
103    /// The `when` parameter allows you to specify when the panic occurs.<br>
104    /// For example, integer overflow panics only in debug mode.
105    pub fn act_panic(
106        &self,
107        when: PanicWhen,
108        function: impl Fn(&mut Message, &mut T) + 'a,
109    ) -> &Self {
110        let mut message = Message::new();
111        let mut value = (self.arrange_function)(&mut message);
112        message.set_current_type(MessageType::Act);
113        let result = panic::catch_unwind(AssertUnwindSafe(|| {
114            function(&mut message, &mut value);
115        }));
116        if result.is_ok() == Self::should_panic(when) {
117            message.set_current_type(MessageType::Assert);
118            message.set("** Skipped **");
119            if result.is_ok() {
120                panic!(Self::format_error_message(&message, NO_PANIC_ERROR));
121            } else {
122                panic!(Self::format_error_message(&message, PANIC_ERROR));
123            }
124        }
125        self
126    }
127
128    /// Define an assertion to check.
129    ///
130    /// The check is done immediately on method call.<br>
131    /// The assertion is tested independently with all defined actions.
132    pub fn assert<A: Assert>(
133        &self,
134        function: impl Fn(&mut Message, T, E) -> A + 'a,
135    ) -> &Self {
136        for act in &self.actions {
137            let mut message = Message::new();
138            let mut value = (self.arrange_function)(&mut message);
139            message.set_current_type(MessageType::Act);
140            act.execute(&mut message, &mut value);
141            let expected = act.expect();
142            message.set_current_type(MessageType::Assert);
143            let result = function(&mut message, value, expected);
144            if !result.success() {
145                panic!(Self::format_error_message(
146                    &message,
147                    &result.error_message()
148                ));
149            }
150        }
151        self
152    }
153
154    /// Defines an assertion that should panic.
155    ///
156    /// The check is done immediately on method call.<br>
157    /// The assertion is tested independently with all defined actions.
158    pub fn assert_panic<A: Assert>(
159        &self,
160        when: PanicWhen,
161        function: impl Fn(&mut Message, T, E) -> A + 'a,
162    ) -> &Self {
163        for act in &self.actions {
164            let mut message = Message::new();
165            let mut value = (self.arrange_function)(&mut message);
166            message.set_current_type(MessageType::Act);
167            act.execute(&mut message, &mut value);
168            let expected = act.expect();
169            message.set_current_type(MessageType::Assert);
170            let result = panic::catch_unwind(AssertUnwindSafe(|| {
171                function(&mut message, value, expected)
172            }));
173            if result.is_ok() == Self::should_panic(when) {
174                if result.is_ok() {
175                    panic!(Self::format_error_message(
176                        &message,
177                        NO_PANIC_ERROR
178                    ));
179                } else {
180                    panic!(Self::format_error_message(&message, PANIC_ERROR));
181                }
182            }
183        }
184        self
185    }
186
187    /// Format the error message to print when a test has failed.
188    fn format_error_message(message: &Message, error: &str) -> String {
189        "\n--------------------------------------------\n".to_string()
190            + &format!("Arrange:\n    {}\n", message.arrange_message())
191            + &format!("Act:\n    {}\n", message.act_message())
192            + &format!("Assert:\n    {}\n", message.assert_message())
193            + &format!("Error:\n    {}\n", error.replace("\n", "\n    "))
194            + "--------------------------------------------\n"
195    }
196
197    /// Tell whether the code should actually panic with the current config.
198    ///
199    /// For example, PanicWhen::Debug will be chosen to test integer overflow.
200    /// So this method will return `true` in debug mode and `false` in release
201    /// mode.
202    fn should_panic(panic_when: PanicWhen) -> bool {
203        #[cfg(debug_assertions)]
204        match panic_when {
205            PanicWhen::Debug | PanicWhen::DebugAndRelease => true,
206            PanicWhen::Release => false,
207        }
208        #[cfg(not(debug_assertions))]
209        match panic_when {
210            PanicWhen::Release | PanicWhen::DebugAndRelease => true,
211            PanicWhen::Debug => false,
212        }
213    }
214}
215
216#[cfg(test)]
217mod tests {
218    use crate::{Equal, PanicWhen, Runner};
219
220    const VALUE_EXAMPLE: usize = 10;
221
222    struct Expected {
223        value: usize,
224    }
225
226    #[test]
227    fn test_ok() {
228        Runner::arrange(|message| {
229            message.set("Initialization");
230            0
231        })
232        .act(
233            |message, value| {
234                message.set("Action");
235                *value = VALUE_EXAMPLE
236            },
237            || Expected {
238                value: VALUE_EXAMPLE,
239            },
240        )
241        .act_panic(PanicWhen::Debug, |message, value| {
242            message.set("Other action");
243            *value -= 1;
244        })
245        .assert_panic(PanicWhen::Debug, |message, mut _value, _expected| {
246            message.set("Evaluation");
247            _value -= VALUE_EXAMPLE + 1;
248            Equal::new(0, 0)
249        })
250        .assert(|message, value, expected| {
251            message.set("Other evaluation");
252            Equal::new(value, expected.value)
253        });
254    }
255
256    #[test]
257    #[should_panic]
258    #[cfg(debug_assertions)]
259    fn test_act_panic_fail() {
260        Runner::arrange(|message| {
261            message.set("Initialization");
262            0
263        })
264        .act(
265            |message, value| {
266                message.set("Action");
267                *value = VALUE_EXAMPLE
268            },
269            || Expected {
270                value: VALUE_EXAMPLE,
271            },
272        )
273        .act_panic(PanicWhen::Debug, |message, value| {
274            message.set("Other action");
275            *value -= 0;
276        })
277        .assert_panic(PanicWhen::Debug, |message, mut _value, _expected| {
278            message.set("Evaluation");
279            _value -= VALUE_EXAMPLE + 1;
280            Equal::new(0, 0)
281        })
282        .assert(|message, value, expected| {
283            message.set("Other evaluation");
284            Equal::new(value, expected.value)
285        });
286    }
287
288    #[test]
289    #[should_panic]
290    #[cfg(debug_assertions)]
291    fn test_assert_panic_fail() {
292        Runner::arrange(|message| {
293            message.set("Initialization");
294            0
295        })
296        .act(
297            |message, value| {
298                message.set("Action");
299                *value = VALUE_EXAMPLE
300            },
301            || Expected {
302                value: VALUE_EXAMPLE,
303            },
304        )
305        .act_panic(PanicWhen::Debug, |message, value| {
306            message.set("Other action");
307            *value -= 1;
308        })
309        .assert_panic(PanicWhen::Debug, |message, mut _value, _expected| {
310            message.set("Evaluation");
311            _value -= 0;
312            Equal::new(0, 0)
313        })
314        .assert(|message, value, expected| {
315            message.set("Other evaluation");
316            Equal::new(value, expected.value)
317        });
318    }
319
320    #[test]
321    #[should_panic]
322    fn test_assert_fail() {
323        Runner::arrange(|message| {
324            message.set("Initialization");
325            0
326        })
327        .act(
328            |message, value| {
329                message.set("Action");
330                *value = VALUE_EXAMPLE
331            },
332            || Expected {
333                value: VALUE_EXAMPLE,
334            },
335        )
336        .act_panic(PanicWhen::Debug, |message, value| {
337            message.set("Other action");
338            *value -= 1;
339        })
340        .assert_panic(PanicWhen::Debug, |message, mut _value, _expected| {
341            message.set("Evaluation");
342            _value -= VALUE_EXAMPLE + 1;
343            Equal::new(0, 0)
344        })
345        .assert(|message, value, expected| {
346            message.set("Other evaluation");
347            Equal::new(value, expected.value + 1)
348        });
349    }
350}