Skip to content

Commit 3a7250f

Browse files
committed
Split summary logic in its own file
1 parent a02195b commit 3a7250f

File tree

7 files changed

+316
-135
lines changed

7 files changed

+316
-135
lines changed

googletest/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,4 @@ ansi_term = "0.12.0"
4040
[dev-dependencies]
4141
indoc = "2"
4242
quickcheck = "1.0.3"
43+
serial_test = "2.0.0"

googletest/src/internal/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,5 @@
1313
// limitations under the License.
1414

1515
#![doc(hidden)]
16-
1716
pub mod source_location;
1817
pub mod test_outcome;

googletest/src/matcher_support/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@
2121
pub(crate) mod count_elements;
2222
pub mod description;
2323
pub(crate) mod edit_distance;
24+
pub(crate) mod summarize_diff;
2425
pub(crate) mod zipped_iterator;
Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
// Copyright 2022 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#[doc(hidden)]
16+
use std::borrow::Cow;
17+
use std::fmt::{Display, Write};
18+
19+
use ansi_term::{Color, Style};
20+
21+
use crate::matcher_support::edit_distance;
22+
23+
/// Returns a string describing how the expected and actual lines differ.
24+
///
25+
/// This is included in a match explanation for [`EqMatcher`] and
26+
/// [`crate::matchers::str_matcher::StrMatcher`].
27+
///
28+
/// If the actual value has at most two lines, or the two differ by more than
29+
/// the maximum edit distance, then this returns the empty string. If the two
30+
/// are equal, it returns a simple statement that they are equal. Otherwise,
31+
/// this constructs a unified diff view of the actual and expected values.
32+
pub(crate) fn create_diff(
33+
expected_debug: &str,
34+
actual_debug: &str,
35+
diff_mode: edit_distance::Mode,
36+
) -> Cow<'static, str> {
37+
if actual_debug.lines().count() < 2 {
38+
// If the actual debug is only one line, then there is no point in doing a
39+
// line-by-line diff.
40+
return "".into();
41+
}
42+
match edit_distance::edit_list(actual_debug.lines(), expected_debug.lines(), diff_mode) {
43+
edit_distance::Difference::Equal => "No difference found between debug strings.".into(),
44+
edit_distance::Difference::Editable(edit_list) => {
45+
format!("\nDifference:{}", edit_list_summary(&edit_list)).into()
46+
}
47+
edit_distance::Difference::Unrelated => "".into(),
48+
}
49+
}
50+
51+
/// Returns a string describing how the expected and actual differ after
52+
/// reversing the lines in each.
53+
///
54+
/// This is similar to [`create_diff`] except that it first reverses the lines
55+
/// in both the expected and actual values, then reverses the constructed edit
56+
/// list. When `diff_mode` is [`edit_distance::Mode::Prefix`], this becomes a
57+
/// diff of the suffix for use by [`ends_with`][crate::matchers::ends_with].
58+
pub(crate) fn create_diff_reversed(
59+
expected_debug: &str,
60+
actual_debug: &str,
61+
diff_mode: edit_distance::Mode,
62+
) -> Cow<'static, str> {
63+
if actual_debug.lines().count() < 2 {
64+
// If the actual debug is only one line, then there is no point in doing a
65+
// line-by-line diff.
66+
return "".into();
67+
}
68+
let mut actual_lines_reversed = actual_debug.lines().collect::<Vec<_>>();
69+
let mut expected_lines_reversed = expected_debug.lines().collect::<Vec<_>>();
70+
actual_lines_reversed.reverse();
71+
expected_lines_reversed.reverse();
72+
match edit_distance::edit_list(actual_lines_reversed, expected_lines_reversed, diff_mode) {
73+
edit_distance::Difference::Equal => "No difference found between debug strings.".into(),
74+
edit_distance::Difference::Editable(mut edit_list) => {
75+
edit_list.reverse();
76+
format!("\nDifference:{}", edit_list_summary(&edit_list)).into()
77+
}
78+
edit_distance::Difference::Unrelated => "".into(),
79+
}
80+
}
81+
82+
enum LineStyle {
83+
Ansi(Style),
84+
Header(char),
85+
}
86+
const NO_COLOR_VAR: &str = "GTEST_RUST_NO_COLOR";
87+
88+
impl LineStyle {
89+
fn style<'a, 'b>(&'a self, line: &'b str) -> StyledLine<'a, 'b> {
90+
StyledLine { style: self, line }
91+
}
92+
93+
fn extra_left_style() -> Self {
94+
if Self::ansi() {
95+
Self::Ansi(Style::new().fg(Color::Red).bold())
96+
} else {
97+
Self::Header('+')
98+
}
99+
}
100+
101+
fn extra_right_style() -> Self {
102+
if Self::ansi() {
103+
Self::Ansi(Style::new().fg(Color::Blue).bold())
104+
} else {
105+
Self::Header('-')
106+
}
107+
}
108+
109+
fn comment_style() -> Self {
110+
if Self::ansi() { Self::Ansi(Style::new().italic()) } else { Self::Header(' ') }
111+
}
112+
113+
fn unchanged_style() -> Self {
114+
if Self::ansi() { Self::Ansi(Style::new()) } else { Self::Header(' ') }
115+
}
116+
117+
fn ansi() -> bool {
118+
std::env::var(NO_COLOR_VAR).is_err()
119+
}
120+
}
121+
122+
struct StyledLine<'a, 'b> {
123+
style: &'a LineStyle,
124+
line: &'b str,
125+
}
126+
127+
impl<'a, 'b> Display for StyledLine<'a, 'b> {
128+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129+
match self.style {
130+
LineStyle::Ansi(style) => {
131+
write!(f, "{}", style.paint(self.line))
132+
}
133+
LineStyle::Header(c) => write!(f, "{}{}", c, self.line),
134+
}
135+
}
136+
}
137+
138+
fn edit_list_summary(edit_list: &[edit_distance::Edit<&str>]) -> String {
139+
let mut summary = String::new();
140+
// Use to collect common line and compress them.
141+
let mut common_line_buffer = vec![];
142+
for edit in edit_list {
143+
let (style, line) = match edit {
144+
edit_distance::Edit::Both(left) => {
145+
common_line_buffer.push(*left);
146+
continue;
147+
}
148+
edit_distance::Edit::ExtraLeft(left) => (LineStyle::extra_left_style(), *left),
149+
edit_distance::Edit::ExtraRight(right) => (LineStyle::extra_right_style(), *right),
150+
edit_distance::Edit::AdditionalLeft => {
151+
(LineStyle::comment_style(), "<---- remaining lines omitted ---->")
152+
}
153+
};
154+
summary.push_str(&compress_common_lines(std::mem::take(&mut common_line_buffer)));
155+
156+
write!(&mut summary, "\n{}", style.style(line)).unwrap();
157+
}
158+
summary.push_str(&compress_common_lines(common_line_buffer));
159+
160+
summary
161+
}
162+
163+
// The number of the lines kept before and after the compressed lines.
164+
const COMMON_LINES_CONTEXT_SIZE: usize = 2;
165+
166+
fn compress_common_lines(common_lines: Vec<&str>) -> String {
167+
if common_lines.len() <= 2 * COMMON_LINES_CONTEXT_SIZE + 1 {
168+
let mut all_lines = String::new();
169+
for line in common_lines {
170+
write!(&mut all_lines, "\n{}", LineStyle::unchanged_style().style(line)).unwrap();
171+
}
172+
return all_lines;
173+
}
174+
175+
let mut truncated_lines = String::new();
176+
177+
for line in &common_lines[0..COMMON_LINES_CONTEXT_SIZE] {
178+
write!(&mut truncated_lines, "\n{}", LineStyle::unchanged_style().style(line)).unwrap();
179+
}
180+
181+
write!(
182+
&mut truncated_lines,
183+
"\n{}",
184+
LineStyle::comment_style().style(&format!(
185+
"<---- {} common lines omitted ---->",
186+
common_lines.len() - 2 * COMMON_LINES_CONTEXT_SIZE
187+
)),
188+
)
189+
.unwrap();
190+
191+
for line in &common_lines[common_lines.len() - COMMON_LINES_CONTEXT_SIZE..common_lines.len()] {
192+
write!(&mut truncated_lines, "\n{}", LineStyle::unchanged_style().style(line)).unwrap();
193+
}
194+
truncated_lines
195+
}
196+
197+
#[cfg(test)]
198+
mod tests {
199+
use super::*;
200+
use crate::{matcher_support::edit_distance::Mode, prelude::*};
201+
use indoc::indoc;
202+
use serial_test::serial;
203+
204+
#[must_use]
205+
fn remove_var() -> TempVar {
206+
let old_value = std::env::var(NO_COLOR_VAR);
207+
std::env::remove_var(NO_COLOR_VAR);
208+
TempVar(old_value.ok())
209+
}
210+
211+
#[must_use]
212+
fn set_var(var: &str) -> TempVar {
213+
let old_value = std::env::var(NO_COLOR_VAR);
214+
std::env::set_var(NO_COLOR_VAR, var);
215+
TempVar(old_value.ok())
216+
}
217+
struct TempVar(Option<String>);
218+
219+
impl Drop for TempVar {
220+
fn drop(&mut self) {
221+
match &self.0 {
222+
Some(old_var) => std::env::set_var(NO_COLOR_VAR, old_var),
223+
None => std::env::remove_var(NO_COLOR_VAR),
224+
}
225+
}
226+
}
227+
228+
// Make a long text with each element of the iterator on one line.
229+
// `collection` must contains at least one element.
230+
fn build_text<T: Display>(mut collection: impl Iterator<Item = T>) -> String {
231+
let mut text = String::new();
232+
write!(&mut text, "{}", collection.next().expect("Provided collection without elements"))
233+
.unwrap();
234+
for item in collection {
235+
write!(&mut text, "\n{}", item).unwrap();
236+
}
237+
text
238+
}
239+
240+
#[test]
241+
fn create_diff_smaller_than_one_line() -> Result<()> {
242+
verify_that!(create_diff("One", "Two", Mode::Exact), eq(""))
243+
}
244+
245+
#[test]
246+
fn create_diff_exact_same() -> Result<()> {
247+
let expected = indoc! {"
248+
One
249+
Two
250+
"};
251+
let actual = indoc! {"
252+
One
253+
Two
254+
"};
255+
verify_that!(
256+
create_diff(expected, actual, Mode::Exact),
257+
eq("No difference found between debug strings.")
258+
)
259+
}
260+
261+
#[test]
262+
fn create_diff_exact_unrelated() -> Result<()> {
263+
verify_that!(create_diff(&build_text(1..500), &build_text(501..1000), Mode::Exact), eq(""))
264+
}
265+
266+
#[test]
267+
#[serial]
268+
fn create_diff_exact_small_difference() -> Result<()> {
269+
let _cleanup = remove_var();
270+
271+
verify_that!(
272+
create_diff(&build_text(1..50), &build_text(1..51), Mode::Exact),
273+
eq(indoc! {
274+
"
275+
276+
Difference:
277+
1
278+
2
279+
\x1B[3m<---- 45 common lines omitted ---->\x1B[0m
280+
48
281+
49
282+
\x1B[1;31m50\x1B[0m"
283+
})
284+
)
285+
}
286+
#[test]
287+
#[serial]
288+
fn create_diff_exact_small_difference_no_color() -> Result<()> {
289+
let _cleanup = set_var("NO_COLOR");
290+
291+
verify_that!(
292+
create_diff(&build_text(1..50), &build_text(1..51), Mode::Exact),
293+
eq(indoc! {
294+
"
295+
296+
Difference:
297+
1
298+
2
299+
<---- 45 common lines omitted ---->
300+
48
301+
49
302+
+50"
303+
})
304+
)
305+
}
306+
307+
}

googletest/src/matchers/eq_deref_of_matcher.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414

1515
use crate::{
1616
matcher::{Matcher, MatcherResult},
17-
matcher_support::edit_distance,
18-
matchers::eq_matcher::create_diff,
17+
matcher_support::{edit_distance, summarize_diff::create_diff},
18+
1919
};
2020
use std::{fmt::Debug, marker::PhantomData, ops::Deref};
2121

0 commit comments

Comments
 (0)