aboutsummaryrefslogtreecommitdiff
path: root/crates/tor-netdoc/src/test_support.rs
blob: 1886667a6a663eeec69655451d71d62142763948 (plain)
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
//! Support functions for testing, also expoeted
// @@ begin test lint list maintained by maint/add_warning @@
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::mixed_attributes_style)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_time_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
#![allow(clippy::string_slice)] // See arti#2571
//! <!-- @@ end test lint list maintained by maint/add_warning @@ -->

use crate::parse2::{NetdocParseableFields, ParseInput, parse_netdoc};
use derive_deftly::Deftly;
use itertools::chain;
use std::fmt::Display;

/// Assert that `$a = $b`; if not, panic with a unidiff
//
// implementation is in fn assert_eq_or_diff, at the bottom of the file
#[cfg(any(test, feature = "testing"))]
#[macro_export]
macro_rules! assert_eq_or_diff {
    { $a:expr, $b:expr $(,)? } => {
        assert_eq_or_diff!($a, $b, "")
    };
    { $a:expr, $b:expr , $($message:tt)*} => {
        $crate::assert_eq_or_diff(
            &$a,
            stringify!($a),
            &$b,
            stringify!($b),
            &format_args!($($message)*),
        )
    };
}

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

    if a == b {
        return;
    }
    let input = InternedInput::new(a, b);
    let mut diff = Diff::compute(Algorithm::Histogram, &input);
    diff.postprocess_lines(&input);
    panic!(
        // rustdoc insists on this unhelpful formatting
        "===== document {a_what} =====
{a}
===== document {b_what} =====
{b}
===== diff ====
{}
===== documents differ: {a_what} != {b_what} =====
{message}
",
        diff.unified_diff(
            &BasicLineDiffPrinter(&input.interner),
            UnifiedDiffConfig::default(),
            &input,
        ),
    );
}

/// Substitute all `re` in `update` with `repl`
///
/// **Note** that `re` is processed in "multiple lines mode"
/// (`(?m)` is prepended.)
///
/// Convenience wrapper around [`regex::Regex::replace_all`].
pub fn regsub(update: &mut String, re: &str, repl: impl regex::Replacer) {
    *update = regex::Regex::new(&format!("(?m){re}"))
        .expect(re)
        .replace_all(update, repl)
        .to_string();
}

/// Parse a test case from a netdoc-style test case string
///
/// `T` must be `NetdocParseableFields`.
/// (a surrounding document type with an intro item will be used internally.)
///
/// The input string is preprocessed:
///
///  - `#`-comments are stripped
///  - each line is trimmed (so the input can be inden ted)
///  - blank lines are removed
pub fn parse_testcase_from_netdoc<T: NetdocParseableFields>(input_doc: &str) -> T {
    #[derive(Deftly)]
    #[derive_deftly(NetdocParseable)]
    struct Document<T: NetdocParseableFields> {
        /// Intro item, not present in test case doc strings
        #[allow(unused)]
        parse_testcase_from_netdoc_intro: (),

        #[deftly(netdoc(flatten))]
        fields: T,
    }

    eprintln!("\n&&&&&&& input test case\n{input_doc}");
    let doc = chain!(
        ["parse-testcase-from-netdoc-intro\n"],
        input_doc
            .lines()
            .map(|l| l.split_once('#').map(|(l, _)| l).unwrap_or(l).trim())
            .filter(|l| !l.is_empty())
            .flat_map(|l| [l, "\n"]),
    )
    .collect::<String>();

    eprintln!(
        "---- tidied \n{}----",
        doc.split_inclusive('\n')
            // show line numbers in case of parse errors, what a faff
            .enumerate()
            .map(|(lno, l)| format!("| {:5} {l}", lno + 1))
            .collect::<String>()
    );

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

    case.fields
}