1
//! Support functions for testing, also expoeted
2
// @@ begin test lint list maintained by maint/add_warning @@
3
#![allow(clippy::bool_assert_comparison)]
4
#![allow(clippy::clone_on_copy)]
5
#![allow(clippy::dbg_macro)]
6
#![allow(clippy::mixed_attributes_style)]
7
#![allow(clippy::print_stderr)]
8
#![allow(clippy::print_stdout)]
9
#![allow(clippy::single_char_pattern)]
10
#![allow(clippy::unwrap_used)]
11
#![allow(clippy::unchecked_time_subtraction)]
12
#![allow(clippy::useless_vec)]
13
#![allow(clippy::needless_pass_by_value)]
14
#![allow(clippy::string_slice)] // See arti#2571
15
//! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
16

            
17
use crate::parse2::{NetdocParseableFields, ParseInput, parse_netdoc};
18
use derive_deftly::Deftly;
19
use itertools::chain;
20
use std::fmt::Display;
21

            
22
/// Assert that `$a = $b`; if not, panic with a unidiff
23
//
24
// implementation is in fn assert_eq_or_diff, at the bottom of the file
25
#[cfg(any(test, feature = "testing"))]
26
#[macro_export]
27
macro_rules! assert_eq_or_diff {
28
    { $a:expr, $b:expr $(,)? } => {
29
        assert_eq_or_diff!($a, $b, "")
30
    };
31
    { $a:expr, $b:expr , $($message:tt)*} => {
32
        $crate::assert_eq_or_diff(
33
            &$a,
34
            stringify!($a),
35
            &$b,
36
            stringify!($b),
37
            &format_args!($($message)*),
38
        )
39
    };
40
}
41

            
42
/// Assert that `a = b`; if not, panic with a unidiff mentioning `a_what`, `b_what` and `message`
43
///
44
/// Normally it is more convenient to use the [`assert_eq_or_diff!`] macro.
45
#[cfg(any(test, feature = "testing"))]
46
835
pub fn assert_eq_or_diff(a: &str, a_what: &str, b: &str, b_what: &str, message: &dyn Display) {
47
    use imara_diff::{Algorithm, BasicLineDiffPrinter, Diff, InternedInput, UnifiedDiffConfig};
48

            
49
835
    if a == b {
50
835
        return;
51
    }
52
    let input = InternedInput::new(a, b);
53
    let mut diff = Diff::compute(Algorithm::Histogram, &input);
54
    diff.postprocess_lines(&input);
55
    panic!(
56
        // rustdoc insists on this unhelpful formatting
57
        "===== document {a_what} =====
58
{a}
59
===== document {b_what} =====
60
{b}
61
===== diff ====
62
{}
63
===== documents differ: {a_what} != {b_what} =====
64
{message}
65
",
66
        diff.unified_diff(
67
            &BasicLineDiffPrinter(&input.interner),
68
            UnifiedDiffConfig::default(),
69
            &input,
70
        ),
71
    );
72
835
}
73

            
74
/// Substitute all `re` in `update` with `repl`
75
///
76
/// **Note** that `re` is processed in "multiple lines mode"
77
/// (`(?m)` is prepended.)
78
///
79
/// Convenience wrapper around [`regex::Regex::replace_all`].
80
182
pub fn regsub(update: &mut String, re: &str, repl: impl regex::Replacer) {
81
182
    *update = regex::Regex::new(&format!("(?m){re}"))
82
182
        .expect(re)
83
182
        .replace_all(update, repl)
84
182
        .to_string();
85
182
}
86

            
87
/// Parse a test case from a netdoc-style test case string
88
///
89
/// `T` must be `NetdocParseableFields`.
90
/// (a surrounding document type with an intro item will be used internally.)
91
///
92
/// The input string is preprocessed:
93
///
94
///  - `#`-comments are stripped
95
///  - each line is trimmed (so the input can be inden ted)
96
///  - blank lines are removed
97
34
pub fn parse_testcase_from_netdoc<T: NetdocParseableFields>(input_doc: &str) -> T {
98
    #[derive(Deftly)]
99
    #[derive_deftly(NetdocParseable)]
100
    struct Document<T: NetdocParseableFields> {
101
        /// Intro item, not present in test case doc strings
102
        #[allow(unused)]
103
        parse_testcase_from_netdoc_intro: (),
104

            
105
        #[deftly(netdoc(flatten))]
106
        fields: T,
107
    }
108

            
109
34
    eprintln!("\n&&&&&&& input test case\n{input_doc}");
110
34
    let doc = chain!(
111
34
        ["parse-testcase-from-netdoc-intro\n"],
112
34
        input_doc
113
34
            .lines()
114
212
            .map(|l| l.split_once('#').map(|(l, _)| l).unwrap_or(l).trim())
115
212
            .filter(|l| !l.is_empty())
116
150
            .flat_map(|l| [l, "\n"]),
117
    )
118
34
    .collect::<String>();
119

            
120
34
    eprintln!(
121
        "---- tidied \n{}----",
122
34
        doc.split_inclusive('\n')
123
            // show line numbers in case of parse errors, what a faff
124
34
            .enumerate()
125
184
            .map(|(lno, l)| format!("| {:5} {l}", lno + 1))
126
34
            .collect::<String>()
127
    );
128

            
129
34
    let pinput = ParseInput::new(&doc, "<input doc>");
130
34
    let case: Document<T> = parse_netdoc(&pinput).expect("parse failed");
131

            
132
34
    case.fields
133
34
}