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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
//! Defines the type `Runner`.

use crate::messages::MessageType;
use crate::runners::act::Act;
use crate::{Assert, Message, PanicWhen};
use std::panic;
use std::panic::AssertUnwindSafe;

const NO_PANIC_ERROR: &str = "Code has not panicked";
const PANIC_ERROR: &str = "Code has panicked";

/// Describes and runs multiple tests.
///
/// # Examples
///
/// ```rust
/// use test4a::{Message, Equal, Runner, PanicWhen};
///
/// fn add_0(message: &mut Message, value: &mut usize) {
///     message.set("Add 0");
///     *value += 0;
/// }
///
/// fn add_1(message: &mut Message, value: &mut usize) {
///     message.set("Add 1");
///     *value += 1;
/// }
///
/// fn subtract_1(message: &mut Message, value: &mut usize) {
///     message.set("Subtract 1");
///     *value -= 1;
/// }
///
/// fn value_expected(
///     message: &mut Message,
///     value: usize,
///     expected: Expected,
/// ) -> Equal<usize> {
///     message.set("Value is expected");
///     Equal::new(value, expected.value)
/// }
///
/// struct Expected {
///     value: usize
/// }
///
///
/// // Test #1
/// Runner::arrange(|message| {
///     message.set("Initial value of 1");
///     1
/// })
/// .act(add_0, || Expected { value: 1 })
/// .act(add_1, || Expected { value: 2 })
/// .act(subtract_1, || Expected { value: 0 })
/// .assert(value_expected);
///
/// // Test #2
/// Runner::arrange(|message| {
///     message.set("Initial value of 42");
///     42
/// })
/// .act(add_0, || Expected { value: 42 })
/// .act(add_1, || Expected { value: 43 })
/// .act(subtract_1, || Expected { value: 41 })
/// .assert(value_expected);
///
/// // ...
/// ```
pub struct Runner<'a, T, E> {
    arrange_function: Box<Fn(&mut Message) -> T + 'a>,
    actions: Vec<Act<'a, T, E>>,
}

impl<'a, T, E> Runner<'a, T, E> {
    /// Constructor.
    ///
    /// `function` defines how the value to test should be initialized.<br>
    /// It isn't called immediately, but only for each defined test.
    pub fn arrange(function: impl Fn(&mut Message) -> T + 'a) -> Self {
        Self {
            arrange_function: Box::new(function),
            actions: Vec::new(),
        }
    }

    /// Define an action to execute after the value to test has been created.
    ///
    /// The Arrange step is executed before each defined Act step to have
    /// independent tests.
    pub fn act(
        &mut self,
        action: impl Fn(&mut Message, &mut T) + 'a,
        expectation: impl Fn() -> E + 'a,
    ) -> &mut Self {
        self.actions.push(Act::new(action, expectation));
        self
    }

    /// Define an action that should panic.
    ///
    /// The check is done immediately on method call.<br>
    /// The `when` parameter allows you to specify when the panic occurs.<br>
    /// For example, integer overflow panics only in debug mode.
    pub fn act_panic(
        &self,
        when: PanicWhen,
        function: impl Fn(&mut Message, &mut T) + 'a,
    ) -> &Self {
        let mut message = Message::new();
        let mut value = (self.arrange_function)(&mut message);
        message.set_current_type(MessageType::Act);
        let result = panic::catch_unwind(AssertUnwindSafe(|| {
            function(&mut message, &mut value);
        }));
        if result.is_ok() == Self::should_panic(when) {
            message.set_current_type(MessageType::Assert);
            message.set("** Skipped **");
            if result.is_ok() {
                panic!(Self::format_error_message(&message, NO_PANIC_ERROR));
            } else {
                panic!(Self::format_error_message(&message, PANIC_ERROR));
            }
        }
        self
    }

    /// Define an assertion to check.
    ///
    /// The check is done immediately on method call.<br>
    /// The assertion is tested independently with all defined actions.
    pub fn assert<A: Assert>(
        &self,
        function: impl Fn(&mut Message, T, E) -> A + 'a,
    ) -> &Self {
        for act in &self.actions {
            let mut message = Message::new();
            let mut value = (self.arrange_function)(&mut message);
            message.set_current_type(MessageType::Act);
            act.execute(&mut message, &mut value);
            let expected = act.expect();
            message.set_current_type(MessageType::Assert);
            let result = function(&mut message, value, expected);
            if !result.success() {
                panic!(Self::format_error_message(
                    &message,
                    &result.error_message()
                ));
            }
        }
        self
    }

    /// Defines an assertion that should panic.
    ///
    /// The check is done immediately on method call.<br>
    /// The assertion is tested independently with all defined actions.
    pub fn assert_panic<A: Assert>(
        &self,
        when: PanicWhen,
        function: impl Fn(&mut Message, T, E) -> A + 'a,
    ) -> &Self {
        for act in &self.actions {
            let mut message = Message::new();
            let mut value = (self.arrange_function)(&mut message);
            message.set_current_type(MessageType::Act);
            act.execute(&mut message, &mut value);
            let expected = act.expect();
            message.set_current_type(MessageType::Assert);
            let result = panic::catch_unwind(AssertUnwindSafe(|| {
                function(&mut message, value, expected)
            }));
            if result.is_ok() == Self::should_panic(when) {
                if result.is_ok() {
                    panic!(Self::format_error_message(
                        &message,
                        NO_PANIC_ERROR
                    ));
                } else {
                    panic!(Self::format_error_message(&message, PANIC_ERROR));
                }
            }
        }
        self
    }

    /// Format the error message to print when a test has failed.
    fn format_error_message(message: &Message, error: &str) -> String {
        "\n--------------------------------------------\n".to_string()
            + &format!("Arrange:\n    {}\n", message.arrange_message())
            + &format!("Act:\n    {}\n", message.act_message())
            + &format!("Assert:\n    {}\n", message.assert_message())
            + &format!("Error:\n    {}\n", error.replace("\n", "\n    "))
            + "--------------------------------------------\n"
    }

    /// Tell whether the code should actually panic with the current config.
    ///
    /// For example, PanicWhen::Debug will be chosen to test integer overflow.
    /// So this method will return `true` in debug mode and `false` in release
    /// mode.
    fn should_panic(panic_when: PanicWhen) -> bool {
        #[cfg(debug_assertions)]
        match panic_when {
            PanicWhen::Debug | PanicWhen::DebugAndRelease => true,
            PanicWhen::Release => false,
        }
        #[cfg(not(debug_assertions))]
        match panic_when {
            PanicWhen::Release | PanicWhen::DebugAndRelease => true,
            PanicWhen::Debug => false,
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::{Equal, PanicWhen, Runner};

    const VALUE_EXAMPLE: usize = 10;

    struct Expected {
        value: usize,
    }

    #[test]
    fn test_ok() {
        Runner::arrange(|message| {
            message.set("Initialization");
            0
        })
        .act(
            |message, value| {
                message.set("Action");
                *value = VALUE_EXAMPLE
            },
            || Expected {
                value: VALUE_EXAMPLE,
            },
        )
        .act_panic(PanicWhen::Debug, |message, value| {
            message.set("Other action");
            *value -= 1;
        })
        .assert_panic(PanicWhen::Debug, |message, mut _value, _expected| {
            message.set("Evaluation");
            _value -= VALUE_EXAMPLE + 1;
            Equal::new(0, 0)
        })
        .assert(|message, value, expected| {
            message.set("Other evaluation");
            Equal::new(value, expected.value)
        });
    }

    #[test]
    #[should_panic]
    #[cfg(debug_assertions)]
    fn test_act_panic_fail() {
        Runner::arrange(|message| {
            message.set("Initialization");
            0
        })
        .act(
            |message, value| {
                message.set("Action");
                *value = VALUE_EXAMPLE
            },
            || Expected {
                value: VALUE_EXAMPLE,
            },
        )
        .act_panic(PanicWhen::Debug, |message, value| {
            message.set("Other action");
            *value -= 0;
        })
        .assert_panic(PanicWhen::Debug, |message, mut _value, _expected| {
            message.set("Evaluation");
            _value -= VALUE_EXAMPLE + 1;
            Equal::new(0, 0)
        })
        .assert(|message, value, expected| {
            message.set("Other evaluation");
            Equal::new(value, expected.value)
        });
    }

    #[test]
    #[should_panic]
    #[cfg(debug_assertions)]
    fn test_assert_panic_fail() {
        Runner::arrange(|message| {
            message.set("Initialization");
            0
        })
        .act(
            |message, value| {
                message.set("Action");
                *value = VALUE_EXAMPLE
            },
            || Expected {
                value: VALUE_EXAMPLE,
            },
        )
        .act_panic(PanicWhen::Debug, |message, value| {
            message.set("Other action");
            *value -= 1;
        })
        .assert_panic(PanicWhen::Debug, |message, mut _value, _expected| {
            message.set("Evaluation");
            _value -= 0;
            Equal::new(0, 0)
        })
        .assert(|message, value, expected| {
            message.set("Other evaluation");
            Equal::new(value, expected.value)
        });
    }

    #[test]
    #[should_panic]
    fn test_assert_fail() {
        Runner::arrange(|message| {
            message.set("Initialization");
            0
        })
        .act(
            |message, value| {
                message.set("Action");
                *value = VALUE_EXAMPLE
            },
            || Expected {
                value: VALUE_EXAMPLE,
            },
        )
        .act_panic(PanicWhen::Debug, |message, value| {
            message.set("Other action");
            *value -= 1;
        })
        .assert_panic(PanicWhen::Debug, |message, mut _value, _expected| {
            message.set("Evaluation");
            _value -= VALUE_EXAMPLE + 1;
            Equal::new(0, 0)
        })
        .assert(|message, value, expected| {
            message.set("Other evaluation");
            Equal::new(value, expected.value + 1)
        });
    }
}