Skip to main content

tor_netdoc/
test_support.rs

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
17use crate::parse2::{NetdocParseableFields, ParseInput, parse_netdoc};
18use derive_deftly::Deftly;
19use itertools::chain;
20use 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]
27macro_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"))]
46pub 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    if a == b {
50        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}
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`].
80pub fn regsub(update: &mut String, re: &str, repl: impl regex::Replacer) {
81    *update = regex::Regex::new(&format!("(?m){re}"))
82        .expect(re)
83        .replace_all(update, repl)
84        .to_string();
85}
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
97pub 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    eprintln!("\n&&&&&&& input test case\n{input_doc}");
110    let doc = chain!(
111        ["parse-testcase-from-netdoc-intro\n"],
112        input_doc
113            .lines()
114            .map(|l| l.split_once('#').map(|(l, _)| l).unwrap_or(l).trim())
115            .filter(|l| !l.is_empty())
116            .flat_map(|l| [l, "\n"]),
117    )
118    .collect::<String>();
119
120    eprintln!(
121        "---- tidied \n{}----",
122        doc.split_inclusive('\n')
123            // show line numbers in case of parse errors, what a faff
124            .enumerate()
125            .map(|(lno, l)| format!("| {:5} {l}", lno + 1))
126            .collect::<String>()
127    );
128
129    let pinput = ParseInput::new(&doc, "<input doc>");
130    let case: Document<T> = parse_netdoc(&pinput).expect("parse failed");
131
132    case.fields
133}