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}