tiny_test/
lib.rs

1use std::fmt::Debug;
2use std::fmt::Write;
3
4/// Executes a series of test-cases, collecting error information.
5///
6/// # Usage
7/// - An iterator of input and expected output data is required.
8/// - By default compares the result and expected result for equality,
9///   a custom assertion function may be provided as sixth parameter.
10/// - While debugging, panics on assertion failure, otherwise collects all failed data in a `Vec`
11///
12/// # Examples
13/// **Basic usage:**
14/// ```rust
15/// #[test]
16/// fn test_parse_fragment_any() {
17///     report_fails(collect_fails!(
18///         // input type
19///         &str,
20///         // output type
21///         IResult<&str, Fragment, ()>,
22///         // test cases in format (input, expected)  
23///         vec![
24///             ("/", Ok(("", Fragment::Separator))),
25///             ("///", Ok(("", Fragment::Separator))),
26///             ("path/to/file", Ok(("/to/file", Fragment::Plain("path"))))
27///         ].into_iter(),
28///         // test function
29///         parse_fragment
30///     ));
31/// }
32/// ```
33///
34/// **Custom assertion:**
35/// ```rust
36/// fn test_in_range() {
37///     report_fails(collect_fails!(
38///         usize,
39///         std::ops::Range<usize>,
40///         usize,
41///         vec![(2, 1..4), (3, 4..6), (0, 1..3)].into_iter(),
42///         |input| input + 2,
43///         |output: &usize, expected: &Range<usize>| expected.contains(output)
44///     ));
45/// }
46/// ```
47#[macro_export]
48macro_rules! collect_fails {
49    ($input:ty, $expected:ty, $result:ty, $cases:expr, $test:expr, $assert:expr) => {{
50        let mut case_id = 0usize;
51        $cases
52            .filter_map(|(input, expected)| {
53                case_id += 1;
54                let result: $result = $test(&input);
55                let assert = $assert(&result, &expected);
56                debug_assert!(
57                    assert,
58                    "test case {}: assertion failed for input `{:#?}`\n\texpected `{:#?}`\n\tresult `{:#?}`\n",
59                    case_id, &input, &expected, &result
60                );
61                if assert {
62                    None
63                } else {
64                    Some((input, expected, result, case_id))
65                }
66            })
67            .collect::<Vec<($input, $expected, $result, usize)>>()
68    }};
69    ($input:ty, $result:ty, $cases:expr, $test:expr) => {
70        collect_fails!($input, $result, $result, $cases, $test, |e, r| e == r)
71    };
72}
73
74/// Constructs a pretty print report of all failed assertions.
75/// - **This method does not check for plausible input!**
76/// - Panics if `fails.is_empty() == false`.
77///
78/// # Usage
79/// Usually used in combination with `collect_fails`
80///
81/// **Basic usage:**
82/// ```rust
83///
84/// report_fails(vec![
85///     ("input string", "expected string", "", 1),
86///     ("hello world!", "hello papa!", "hello mom!", 2),
87/// ])
88///
89/// // One or more assertions failed:
90/// // test case 1: assertion failed for input `"input string"`
91/// //         expected `"expected string"`
92/// //         result `""`
93
94/// // test case 2: assertion failed for input `"hello world!"`
95/// //         expected `"hello papa!"`
96// //         result `"hello mom!"`
97///
98/// ```
99pub fn report_fails<I: Debug, E: Debug, R: Debug>(fails: Vec<(I, E, R, usize)>) {
100    if fails.is_empty() {
101        return;
102    }
103    let mut report = String::with_capacity(1024);
104    for (input, expected, result, case_id) in fails {
105        if writeln!(
106                &mut report,
107                "test case {}: assertion failed for input `{:#?}`\n\texpected `{:#?}`\n\tresult `{:#?}`\n",
108                case_id, input, expected, result
109            )
110            .is_err()
111            {
112                report += &format!("test case {}: assertion failed, unable to print message\n\n", case_id);
113            };
114    }
115    panic!("One or more assertions failed:\n{}", report);
116}