assert_text/
lib.rs

1/*!
2the testing macro tools.
3
4This checks that strings are equal.
5You will see different characters if that is different.
6
7# Features
8
9- assert_text_eq!(txt1, txt2)
10- assert_text_starts_with!(txt1, txt2)
11- assert_text_ends_with!(txt1, txt2)
12- assert_text_match!(txt1, regex_text2)
13- minimum support rustc 1.56.1 (59eed8a2a 2021-11-01)
14
15*/
16
17#[macro_export]
18macro_rules! assert_text_eq {
19    ($left: expr, $right: expr) => {
20        if $left != $right {
21            let orig = $right;
22            let edit = &$left[0..];
23            $crate::print_diff_github_style(orig, edit);
24            panic!("assertion failed")
25        };
26    };
27}
28
29#[macro_export]
30macro_rules! assert_text_starts_with {
31    ($left: expr, $right: expr) => {
32        if !$left.starts_with($right) {
33            let ll = $left.len();
34            let rl = $right.len();
35            let orig = $right;
36            let edit = &$left[0..ll.min(rl)];
37            $crate::print_diff_github_style(orig, edit);
38            panic!("assertion failed")
39        };
40    };
41}
42
43#[macro_export]
44macro_rules! assert_text_ends_with {
45    ($left: expr, $right: expr) => {
46        if !$left.ends_with($right) {
47            let ll = $left.len();
48            let rl = $right.len();
49            let orig = $right;
50            let edit = &$left[if ll > rl { ll - rl } else { 0 }..];
51            $crate::print_diff_github_style(orig, edit);
52            panic!("assertion failed")
53        };
54    };
55}
56
57#[macro_export]
58macro_rules! assert_text_match {
59    ($left: expr, $right: expr) => {
60        let re = regex::Regex::new($right).unwrap();
61        if !re.is_match($left) {
62            assert_eq!($left, concat!("not match of regex: \"", $right, "\""));
63        };
64    };
65}
66
67use difference::{Changeset, Difference};
68use std::string::ToString;
69
70pub fn print_diff_github_style(text1: &str, text2: &str) {
71    //
72    let color_green = "\x1b[32m";
73    let color_red = "\x1b[31m";
74    let color_bright_green = "\x1b[1;32m";
75    let color_reverse_red = "\x1b[31;7m";
76    let color_reverse_green = "\x1b[32;7m";
77    let color_end = "\x1b[0m";
78    //
79    let mut out_s = String::new();
80    //
81    let Changeset { diffs, .. } = Changeset::new(text1, text2, "\n");
82    //
83    for i in 0..diffs.len() {
84        let s = match diffs[i] {
85            Difference::Same(ref y) => format_diff_line_same(y),
86            Difference::Add(ref y) => {
87                let opt = if i > 0 {
88                    if let Difference::Rem(ref x) = diffs[i - 1] {
89                        Some(format_diff_add_rem(
90                            "+",
91                            x,
92                            y,
93                            color_green,
94                            color_reverse_green,
95                            color_end,
96                        ))
97                    } else {
98                        None
99                    }
100                } else {
101                    None
102                };
103                match opt {
104                    Some(a) => a,
105                    None => format_diff_line_mark("+", y, color_bright_green, color_end),
106                }
107            }
108            Difference::Rem(ref y) => {
109                let opt = if i < diffs.len() - 1 {
110                    if let Difference::Add(ref x) = diffs[i + 1] {
111                        Some(format_diff_add_rem(
112                            "-",
113                            x,
114                            y,
115                            color_red,
116                            color_reverse_red,
117                            color_end,
118                        ))
119                    } else {
120                        None
121                    }
122                } else {
123                    None
124                };
125                match opt {
126                    Some(a) => a,
127                    None => format_diff_line_mark("-", y, color_red, color_end),
128                }
129            }
130        };
131        out_s.push_str(s.as_str());
132    }
133    //
134    print!("{}", out_s.as_str());
135}
136
137#[inline(never)]
138fn format_diff_line_same(y: &str) -> String {
139    let mut s = String::with_capacity(y.len() + 2);
140    for line in y.split_terminator('\n') {
141        s.reserve(line.len() + 2);
142        s.push(' ');
143        s.push_str(line);
144        s.push('\n');
145    }
146    s
147}
148
149#[inline(never)]
150fn format_diff_line_mark(
151    mark: &str, // "+" or "-"
152    y: &str,
153    color_start: &str,
154    color_end: &str,
155) -> String {
156    let mut s = String::with_capacity(y.len() + 2);
157    for line in y.split_terminator('\n') {
158        s.reserve(line.len() + 2);
159        s.push_str(color_start);
160        s.push_str(mark);
161        s.push_str(line);
162        s.push_str(color_end);
163        s.push('\n');
164    }
165    s
166}
167
168#[inline(never)]
169fn format_diff_add_rem(
170    mark: &str, // "+" or "-"
171    x: &str,
172    y: &str,
173    color_fore: &str,
174    color_reverse: &str,
175    color_end: &str,
176) -> String {
177    //
178    #[derive(PartialEq, Copy, Clone)]
179    enum Cattr {
180        None,
181        Fore,
182        Reve,
183    }
184    //
185    let mut ca_v: Vec<(Cattr, String)> = vec![(Cattr::Fore, mark.to_string())];
186    //
187    let Changeset { diffs, .. } = Changeset::new(x, y, " ");
188    for c in diffs {
189        match c {
190            Difference::Same(ref z) => {
191                for line in z.split_terminator('\n') {
192                    ca_v.push((Cattr::Fore, line.to_string()));
193                    ca_v.push((Cattr::None, "\n".to_string()));
194                    ca_v.push((Cattr::Fore, mark.to_string()));
195                }
196                let bytes = z.as_bytes();
197                let len = bytes.len();
198                if len >= 1 && bytes[len - 1] != b'\n' {
199                    ca_v.pop();
200                    ca_v.pop();
201                }
202                ca_v.push((Cattr::Fore, " ".to_string()));
203            }
204            Difference::Add(ref z) => {
205                for line in z.split_terminator('\n') {
206                    ca_v.push((Cattr::Reve, line.to_string()));
207                    ca_v.push((Cattr::None, "\n".to_string()));
208                    ca_v.push((Cattr::Fore, mark.to_string()));
209                }
210                let bytes = z.as_bytes();
211                let len = bytes.len();
212                if len >= 1 && bytes[len - 1] != b'\n' {
213                    ca_v.pop();
214                    ca_v.pop();
215                }
216                ca_v.push((Cattr::Fore, " ".to_string()));
217            }
218            _ => {}
219        };
220    }
221    //
222    let mut out_s = String::with_capacity(x.len().max(y.len()) * 2);
223    let mut prev_a: Cattr = Cattr::None;
224    for (cat, st) in &ca_v {
225        //
226        if prev_a != *cat {
227            if prev_a != Cattr::None {
228                out_s.push_str(color_end)
229            }
230            if *cat == Cattr::Fore {
231                out_s.push_str(color_fore);
232            } else if *cat == Cattr::Reve {
233                out_s.push_str(color_reverse);
234            }
235            prev_a = *cat;
236        }
237        out_s.push_str(st.as_str());
238    }
239    if prev_a != Cattr::None {
240        out_s.push_str(color_end);
241    }
242    out_s.push('\n');
243    //
244    out_s
245}