From 8ed42d04b98f5a5e332bd2d458ef86d5fcbe2fde Mon Sep 17 00:00:00 2001 From: Bastien Jacot-Guillarmod Date: Fri, 16 Jun 2023 10:53:00 +0200 Subject: [PATCH] Use ANSI color code to represent difference line. --- googletest/Cargo.toml | 2 + googletest/src/matcher_support/mod.rs | 1 + .../src/matcher_support/summarize_diff.rs | 307 ++++++++++++++++++ googletest/src/matchers/display_matcher.rs | 6 +- .../src/matchers/eq_deref_of_matcher.rs | 24 +- googletest/src/matchers/eq_matcher.rs | 267 +++++---------- googletest/src/matchers/str_matcher.rs | 82 +++-- 7 files changed, 441 insertions(+), 248 deletions(-) create mode 100644 googletest/src/matcher_support/summarize_diff.rs diff --git a/googletest/Cargo.toml b/googletest/Cargo.toml index 52efcaa3..52e28944 100644 --- a/googletest/Cargo.toml +++ b/googletest/Cargo.toml @@ -35,7 +35,9 @@ googletest_macro = { path = "../googletest_macro", version = "0.8.0" } anyhow = { version = "1", optional = true } num-traits = "0.2.15" regex = "1.6.0" +ansi_term = "0.12.0" [dev-dependencies] indoc = "2" quickcheck = "1.0.3" +serial_test = "2.0.0" diff --git a/googletest/src/matcher_support/mod.rs b/googletest/src/matcher_support/mod.rs index 3d298226..8c301618 100644 --- a/googletest/src/matcher_support/mod.rs +++ b/googletest/src/matcher_support/mod.rs @@ -21,4 +21,5 @@ pub(crate) mod count_elements; pub mod description; pub(crate) mod edit_distance; +pub(crate) mod summarize_diff; pub(crate) mod zipped_iterator; diff --git a/googletest/src/matcher_support/summarize_diff.rs b/googletest/src/matcher_support/summarize_diff.rs new file mode 100644 index 00000000..14914478 --- /dev/null +++ b/googletest/src/matcher_support/summarize_diff.rs @@ -0,0 +1,307 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[doc(hidden)] +use std::borrow::Cow; +use std::fmt::{Display, Write}; + +use ansi_term::{Color, Style}; + +use crate::matcher_support::edit_distance; + +/// Returns a string describing how the expected and actual lines differ. +/// +/// This is included in a match explanation for [`EqMatcher`] and +/// [`crate::matchers::str_matcher::StrMatcher`]. +/// +/// If the actual value has at most two lines, or the two differ by more than +/// the maximum edit distance, then this returns the empty string. If the two +/// are equal, it returns a simple statement that they are equal. Otherwise, +/// this constructs a unified diff view of the actual and expected values. +pub(crate) fn create_diff( + expected_debug: &str, + actual_debug: &str, + diff_mode: edit_distance::Mode, +) -> Cow<'static, str> { + if actual_debug.lines().count() < 2 { + // If the actual debug is only one line, then there is no point in doing a + // line-by-line diff. + return "".into(); + } + match edit_distance::edit_list(actual_debug.lines(), expected_debug.lines(), diff_mode) { + edit_distance::Difference::Equal => "No difference found between debug strings.".into(), + edit_distance::Difference::Editable(edit_list) => { + format!("\nDifference:{}", edit_list_summary(&edit_list)).into() + } + edit_distance::Difference::Unrelated => "".into(), + } +} + +/// Returns a string describing how the expected and actual differ after +/// reversing the lines in each. +/// +/// This is similar to [`create_diff`] except that it first reverses the lines +/// in both the expected and actual values, then reverses the constructed edit +/// list. When `diff_mode` is [`edit_distance::Mode::Prefix`], this becomes a +/// diff of the suffix for use by [`ends_with`][crate::matchers::ends_with]. +pub(crate) fn create_diff_reversed( + expected_debug: &str, + actual_debug: &str, + diff_mode: edit_distance::Mode, +) -> Cow<'static, str> { + if actual_debug.lines().count() < 2 { + // If the actual debug is only one line, then there is no point in doing a + // line-by-line diff. + return "".into(); + } + let mut actual_lines_reversed = actual_debug.lines().collect::>(); + let mut expected_lines_reversed = expected_debug.lines().collect::>(); + actual_lines_reversed.reverse(); + expected_lines_reversed.reverse(); + match edit_distance::edit_list(actual_lines_reversed, expected_lines_reversed, diff_mode) { + edit_distance::Difference::Equal => "No difference found between debug strings.".into(), + edit_distance::Difference::Editable(mut edit_list) => { + edit_list.reverse(); + format!("\nDifference:{}", edit_list_summary(&edit_list)).into() + } + edit_distance::Difference::Unrelated => "".into(), + } +} + +enum LineStyle { + Ansi(Style), + Header(char), +} +const NO_COLOR_VAR: &str = "GTEST_RUST_NO_COLOR"; + +impl LineStyle { + fn style<'a, 'b>(&'a self, line: &'b str) -> StyledLine<'a, 'b> { + StyledLine { style: self, line } + } + + fn extra_left_style() -> Self { + if Self::ansi() { + Self::Ansi(Style::new().fg(Color::Red).bold()) + } else { + Self::Header('+') + } + } + + fn extra_right_style() -> Self { + if Self::ansi() { + Self::Ansi(Style::new().fg(Color::Blue).bold()) + } else { + Self::Header('-') + } + } + + fn comment_style() -> Self { + if Self::ansi() { Self::Ansi(Style::new().italic()) } else { Self::Header(' ') } + } + + fn unchanged_style() -> Self { + if Self::ansi() { Self::Ansi(Style::new()) } else { Self::Header(' ') } + } + + fn ansi() -> bool { + std::env::var(NO_COLOR_VAR).is_err() + } +} + +struct StyledLine<'a, 'b> { + style: &'a LineStyle, + line: &'b str, +} + +impl<'a, 'b> Display for StyledLine<'a, 'b> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.style { + LineStyle::Ansi(style) => { + write!(f, "{}", style.paint(self.line)) + } + LineStyle::Header(c) => write!(f, "{}{}", c, self.line), + } + } +} + +fn edit_list_summary(edit_list: &[edit_distance::Edit<&str>]) -> String { + let mut summary = String::new(); + // Use to collect common line and compress them. + let mut common_line_buffer = vec![]; + for edit in edit_list { + let (style, line) = match edit { + edit_distance::Edit::Both(left) => { + common_line_buffer.push(*left); + continue; + } + edit_distance::Edit::ExtraLeft(left) => (LineStyle::extra_left_style(), *left), + edit_distance::Edit::ExtraRight(right) => (LineStyle::extra_right_style(), *right), + edit_distance::Edit::AdditionalLeft => { + (LineStyle::comment_style(), "<---- remaining lines omitted ---->") + } + }; + summary.push_str(&compress_common_lines(std::mem::take(&mut common_line_buffer))); + + write!(&mut summary, "\n{}", style.style(line)).unwrap(); + } + summary.push_str(&compress_common_lines(common_line_buffer)); + + summary +} + +// The number of the lines kept before and after the compressed lines. +const COMMON_LINES_CONTEXT_SIZE: usize = 2; + +fn compress_common_lines(common_lines: Vec<&str>) -> String { + if common_lines.len() <= 2 * COMMON_LINES_CONTEXT_SIZE + 1 { + let mut all_lines = String::new(); + for line in common_lines { + write!(&mut all_lines, "\n{}", LineStyle::unchanged_style().style(line)).unwrap(); + } + return all_lines; + } + + let mut truncated_lines = String::new(); + + for line in &common_lines[0..COMMON_LINES_CONTEXT_SIZE] { + write!(&mut truncated_lines, "\n{}", LineStyle::unchanged_style().style(line)).unwrap(); + } + + write!( + &mut truncated_lines, + "\n{}", + LineStyle::comment_style().style(&format!( + "<---- {} common lines omitted ---->", + common_lines.len() - 2 * COMMON_LINES_CONTEXT_SIZE + )), + ) + .unwrap(); + + for line in &common_lines[common_lines.len() - COMMON_LINES_CONTEXT_SIZE..common_lines.len()] { + write!(&mut truncated_lines, "\n{}", LineStyle::unchanged_style().style(line)).unwrap(); + } + truncated_lines +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{matcher_support::edit_distance::Mode, prelude::*}; + use indoc::indoc; + use serial_test::serial; + + #[must_use] + fn remove_var() -> TempVar { + let old_value = std::env::var(NO_COLOR_VAR); + std::env::remove_var(NO_COLOR_VAR); + TempVar(old_value.ok()) + } + + #[must_use] + fn set_var(var: &str) -> TempVar { + let old_value = std::env::var(NO_COLOR_VAR); + std::env::set_var(NO_COLOR_VAR, var); + TempVar(old_value.ok()) + } + struct TempVar(Option); + + impl Drop for TempVar { + fn drop(&mut self) { + match &self.0 { + Some(old_var) => std::env::set_var(NO_COLOR_VAR, old_var), + None => std::env::remove_var(NO_COLOR_VAR), + } + } + } + + // Make a long text with each element of the iterator on one line. + // `collection` must contains at least one element. + fn build_text(mut collection: impl Iterator) -> String { + let mut text = String::new(); + write!(&mut text, "{}", collection.next().expect("Provided collection without elements")) + .unwrap(); + for item in collection { + write!(&mut text, "\n{}", item).unwrap(); + } + text + } + + #[test] + fn create_diff_smaller_than_one_line() -> Result<()> { + verify_that!(create_diff("One", "Two", Mode::Exact), eq("")) + } + + #[test] + fn create_diff_exact_same() -> Result<()> { + let expected = indoc! {" + One + Two + "}; + let actual = indoc! {" + One + Two + "}; + verify_that!( + create_diff(expected, actual, Mode::Exact), + eq("No difference found between debug strings.") + ) + } + + #[test] + fn create_diff_exact_unrelated() -> Result<()> { + verify_that!(create_diff(&build_text(1..500), &build_text(501..1000), Mode::Exact), eq("")) + } + + #[test] + #[serial] + fn create_diff_exact_small_difference() -> Result<()> { + let _cleanup = remove_var(); + + verify_that!( + create_diff(&build_text(1..50), &build_text(1..51), Mode::Exact), + eq(indoc! { + " + + Difference: + 1 + 2 + \x1B[3m<---- 45 common lines omitted ---->\x1B[0m + 48 + 49 + \x1B[1;31m50\x1B[0m" + }) + ) + } + #[test] + #[serial] + fn create_diff_exact_small_difference_no_color() -> Result<()> { + let _cleanup = set_var("NO_COLOR"); + + verify_that!( + create_diff(&build_text(1..50), &build_text(1..51), Mode::Exact), + eq(indoc! { + " + + Difference: + 1 + 2 + <---- 45 common lines omitted ----> + 48 + 49 + +50" + }) + ) + } + +} diff --git a/googletest/src/matchers/display_matcher.rs b/googletest/src/matchers/display_matcher.rs index d1faadac..b217c94a 100644 --- a/googletest/src/matchers/display_matcher.rs +++ b/googletest/src/matchers/display_matcher.rs @@ -112,9 +112,9 @@ mod tests { " which displays as a string which isn't equal to \"123\\n345\" Difference: - 123 - +234 - -345 + 123 + \x1B[1;31m234\x1B[0m + \x1B[1;34m345\x1B[0m " )))) ) diff --git a/googletest/src/matchers/eq_deref_of_matcher.rs b/googletest/src/matchers/eq_deref_of_matcher.rs index 3e10b77a..8e669bc4 100644 --- a/googletest/src/matchers/eq_deref_of_matcher.rs +++ b/googletest/src/matchers/eq_deref_of_matcher.rs @@ -14,8 +14,8 @@ use crate::{ matcher::{Matcher, MatcherResult}, - matcher_support::edit_distance, - matchers::eq_matcher::create_diff, + matcher_support::{edit_distance, summarize_diff::create_diff}, + }; use std::{fmt::Debug, marker::PhantomData, ops::Deref}; @@ -136,17 +136,17 @@ mod tests { verify_that!( result, err(displays_as(contains_substring(indoc! { - r#" - Actual: Strukt { int: 123, string: "something" }, - which isn't equal to Strukt { int: 321, string: "someone" } + " + Actual: Strukt { int: 123, string: \"something\" }, + which isn't equal to Strukt { int: 321, string: \"someone\" } Difference: - Strukt { - + int: 123, - - int: 321, - + string: "something", - - string: "someone", - } - "#}))) + Strukt { + \x1B[1;31m int: 123,\x1B[0m + \x1B[1;34m int: 321,\x1B[0m + \x1B[1;31m string: \"something\",\x1B[0m + \x1B[1;34m string: \"someone\",\x1B[0m + } + "}))) ) } } diff --git a/googletest/src/matchers/eq_matcher.rs b/googletest/src/matchers/eq_matcher.rs index a8be46e5..f3705128 100644 --- a/googletest/src/matchers/eq_matcher.rs +++ b/googletest/src/matchers/eq_matcher.rs @@ -12,9 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. + + use crate::matcher::{Matcher, MatcherResult}; use crate::matcher_support::edit_distance; -use std::{borrow::Cow, fmt::Debug, marker::PhantomData}; +use crate::matcher_support::summarize_diff::create_diff; + +use std::{fmt::Debug, marker::PhantomData}; /// Matches a value equal (in the sense of `==`) to `expected`. /// @@ -119,125 +123,6 @@ impl + Debug> Matcher for EqMatcher { } } -/// Returns a string describing how the expected and actual lines differ. -/// -/// This is included in a match explanation for [`EqMatcher`] and -/// [`crate::matchers::str_matcher::StrMatcher`]. -/// -/// If the actual value has at most two lines, or the two differ by more than -/// the maximum edit distance, then this returns the empty string. If the two -/// are equal, it returns a simple statement that they are equal. Otherwise, -/// this constructs a unified diff view of the actual and expected values. -pub(super) fn create_diff( - expected_debug: &str, - actual_debug: &str, - diff_mode: edit_distance::Mode, -) -> Cow<'static, str> { - if actual_debug.lines().count() < 2 { - // If the actual debug is only one line, then there is no point in doing a - // line-by-line diff. - return "".into(); - } - match edit_distance::edit_list(actual_debug.lines(), expected_debug.lines(), diff_mode) { - edit_distance::Difference::Equal => "No difference found between debug strings.".into(), - edit_distance::Difference::Editable(edit_list) => { - format!("\nDifference:{}", edit_list_summary(&edit_list)).into() - } - edit_distance::Difference::Unrelated => "".into(), - } -} - -/// Returns a string describing how the expected and actual differ after -/// reversing the lines in each. -/// -/// This is similar to [`create_diff`] except that it first reverses the lines -/// in both the expected and actual values, then reverses the constructed edit -/// list. When `diff_mode` is [`edit_distance::Mode::Prefix`], this becomes a -/// diff of the suffix for use by [`ends_with`][crate::matchers::ends_with]. -pub(super) fn create_diff_reversed( - expected_debug: &str, - actual_debug: &str, - diff_mode: edit_distance::Mode, -) -> Cow<'static, str> { - if actual_debug.lines().count() < 2 { - // If the actual debug is only one line, then there is no point in doing a - // line-by-line diff. - return "".into(); - } - let mut actual_lines_reversed = actual_debug.lines().collect::>(); - let mut expected_lines_reversed = expected_debug.lines().collect::>(); - actual_lines_reversed.reverse(); - expected_lines_reversed.reverse(); - match edit_distance::edit_list(actual_lines_reversed, expected_lines_reversed, diff_mode) { - edit_distance::Difference::Equal => "No difference found between debug strings.".into(), - edit_distance::Difference::Editable(mut edit_list) => { - edit_list.reverse(); - format!("\nDifference:{}", edit_list_summary(&edit_list)).into() - } - edit_distance::Difference::Unrelated => "".into(), - } -} - -fn edit_list_summary(edit_list: &[edit_distance::Edit<&str>]) -> String { - let mut summary = String::new(); - // Use to collect common line and compress them. - let mut common_line_buffer = vec![]; - for edit in edit_list { - let (start, line) = match edit { - edit_distance::Edit::Both(left) => { - common_line_buffer.push(*left); - continue; - } - edit_distance::Edit::ExtraLeft(left) => ("+", *left), - edit_distance::Edit::ExtraRight(right) => ("-", *right), - edit_distance::Edit::AdditionalLeft => ("<---- remaining lines omitted ---->", ""), - }; - summary.push_str(&compress_common_lines(std::mem::take(&mut common_line_buffer))); - - summary.push('\n'); - summary.push_str(start); - summary.push_str(line); - } - summary.push_str(&compress_common_lines(common_line_buffer)); - - summary -} - -// The number of the lines kept before and after the compressed lines. -const COMMON_LINES_CONTEXT_SIZE: usize = 2; - -fn compress_common_lines(common_lines: Vec<&str>) -> String { - if common_lines.len() <= 2 * COMMON_LINES_CONTEXT_SIZE + 1 { - let mut all_lines = String::new(); - for line in common_lines { - all_lines.push('\n'); - all_lines.push(' '); - all_lines.push_str(line); - } - return all_lines; - } - - let mut truncated_lines = String::new(); - - for line in &common_lines[0..COMMON_LINES_CONTEXT_SIZE] { - truncated_lines.push('\n'); - truncated_lines.push(' '); - truncated_lines.push_str(line); - } - - truncated_lines.push_str(&format!( - "\n<---- {} common lines omitted ---->", - common_lines.len() - 2 * COMMON_LINES_CONTEXT_SIZE - )); - - for line in &common_lines[common_lines.len() - COMMON_LINES_CONTEXT_SIZE..common_lines.len()] { - truncated_lines.push('\n'); - truncated_lines.push(' '); - truncated_lines.push_str(line); - } - truncated_lines -} - fn is_multiline_string_debug(string: &str) -> bool { string.starts_with('"') && string.ends_with('"') @@ -292,17 +177,17 @@ mod tests { verify_that!( result, err(displays_as(contains_substring(indoc! { - r#" - Actual: Strukt { int: 123, string: "something" }, - which isn't equal to Strukt { int: 321, string: "someone" } + " + Actual: Strukt { int: 123, string: \"something\" }, + which isn't equal to Strukt { int: 321, string: \"someone\" } Difference: - Strukt { - + int: 123, - - int: 321, - + string: "something", - - string: "someone", - } - "#}))) + Strukt { + \x1B[1;31m int: 123,\x1B[0m + \x1B[1;34m int: 321,\x1B[0m + \x1B[1;31m string: \"something\",\x1B[0m + \x1B[1;34m string: \"someone\",\x1B[0m + } + "}))) ) } @@ -312,19 +197,19 @@ mod tests { verify_that!( result, err(displays_as(contains_substring(indoc! { - r#" + " Value of: vec![1, 2, 3] Expected: is equal to [1, 3, 4] Actual: [1, 2, 3], which isn't equal to [1, 3, 4] Difference: - [ - 1, - + 2, - 3, - - 4, - ] - "#}))) + [ + 1, + \x1B[1;31m 2,\x1B[0m + 3, + \x1B[1;34m 4,\x1B[0m + ] + "}))) ) } @@ -334,20 +219,20 @@ mod tests { verify_that!( result, err(displays_as(contains_substring(indoc! { - r#" + " Value of: vec![1, 2, 3, 4, 5] Expected: is equal to [1, 3, 5] Actual: [1, 2, 3, 4, 5], which isn't equal to [1, 3, 5] Difference: - [ - 1, - + 2, - 3, - + 4, - 5, - ] - "#}))) + [ + 1, + \x1B[1;31m 2,\x1B[0m + 3, + \x1B[1;31m 4,\x1B[0m + 5, + ] + "}))) ) } @@ -359,17 +244,17 @@ mod tests { err(displays_as(contains_substring(indoc! { " Difference: - [ - + 1, - + 2, - 3, - 4, - <---- 43 common lines omitted ----> - 48, - 49, - - 50, - - 51, - ]"}))) + [ + \x1B[1;31m 1,\x1B[0m + \x1B[1;31m 2,\x1B[0m + 3, + 4, + \x1B[3m<---- 43 common lines omitted ---->\x1B[0m + 48, + 49, + \x1B[1;34m 50,\x1B[0m + \x1B[1;34m 51,\x1B[0m + ]"}))) ) } @@ -381,17 +266,17 @@ mod tests { err(displays_as(contains_substring(indoc! { " Difference: - [ - + 1, - + 2, - 3, - 4, - 5, - 6, - 7, - - 8, - - 9, - ]"}))) + [ + \x1B[1;31m 1,\x1B[0m + \x1B[1;31m 2,\x1B[0m + 3, + 4, + 5, + 6, + 7, + \x1B[1;34m 8,\x1B[0m + \x1B[1;34m 9,\x1B[0m + ]"}))) ) } @@ -403,14 +288,14 @@ mod tests { err(displays_as(contains_substring(indoc! { " Difference: - [ - 1, - <---- 46 common lines omitted ----> - 48, - 49, - - 50, - - 51, - ]"}))) + [ + 1, + \x1B[3m<---- 46 common lines omitted ---->\x1B[0m + 48, + 49, + \x1B[1;34m 50,\x1B[0m + \x1B[1;34m 51,\x1B[0m + ]"}))) ) } @@ -422,14 +307,14 @@ mod tests { err(displays_as(contains_substring(indoc! { " Difference: - [ - + 1, - + 2, - 3, - 4, - <---- 46 common lines omitted ----> - 51, - ]"}))) + [ + \x1B[1;31m 1,\x1B[0m + \x1B[1;31m 2,\x1B[0m + 3, + 4, + \x1B[3m<---- 46 common lines omitted ---->\x1B[0m + 51, + ]"}))) ) } @@ -472,12 +357,12 @@ mod tests { verify_that!( result, err(displays_as(contains_substring(indoc!( - r#" - First line - +Second line - -Second lines - Third line - "# + " + First line + \x1B[1;31mSecond line\x1B[0m + \x1B[1;34mSecond lines\x1B[0m + Third line + " )))) ) } diff --git a/googletest/src/matchers/str_matcher.rs b/googletest/src/matchers/str_matcher.rs index d1391cea..df4a69f5 100644 --- a/googletest/src/matchers/str_matcher.rs +++ b/googletest/src/matchers/str_matcher.rs @@ -14,10 +14,10 @@ use crate::{ matcher::{Matcher, MatcherResult}, - matcher_support::edit_distance, + matcher_support::{edit_distance, summarize_diff::{create_diff_reversed, create_diff}}, matchers::{ eq_deref_of_matcher::EqDerefOfMatcher, - eq_matcher::{create_diff, create_diff_reversed, EqMatcher}, + eq_matcher::{ EqMatcher}, }, }; use std::borrow::Cow; @@ -973,10 +973,10 @@ mod tests { result, err(displays_as(contains_substring(indoc!( " - First line - +Second line - -Second lines - Third line + First line + \x1B[1;31mSecond line\x1B[0m + \x1B[1;34mSecond lines\x1B[0m + Third line " )))) ) @@ -1006,11 +1006,11 @@ mod tests { result, err(displays_as(contains_substring(indoc!( " - First line - +Second line - -Second lines - Third line - <---- remaining lines omitted ----> + First line + \x1B[1;31mSecond line\x1B[0m + \x1B[1;34mSecond lines\x1B[0m + Third line + \x1B[3m<---- remaining lines omitted ---->\x1B[0m " )))) ) @@ -1039,11 +1039,10 @@ mod tests { result, err(displays_as(contains_substring(indoc!( " - Difference: - First line - +Second line - -Second lines - <---- remaining lines omitted ----> + First line + \x1B[1;31mSecond line\x1B[0m + \x1B[1;34mSecond lines\x1B[0m + \x1B[3m<---- remaining lines omitted ---->\x1B[0m " )))) ) @@ -1074,11 +1073,11 @@ mod tests { err(displays_as(contains_substring(indoc!( " Difference: - <---- remaining lines omitted ----> - Second line - -Third lines - +Third line - Fourth line + \x1B[3m<---- remaining lines omitted ---->\x1B[0m + Second line + \x1B[1;34mThird lines\x1B[0m + \x1B[1;31mThird line\x1B[0m + Fourth line " )))) ) @@ -1111,13 +1110,12 @@ mod tests { err(displays_as(contains_substring(indoc!( " Difference: - <---- remaining lines omitted ----> - Second line - -Third lines - +Third line - Fourth line - <---- remaining lines omitted ----> - " + \x1B[3m<---- remaining lines omitted ---->\x1B[0m + Second line + \x1B[1;34mThird lines\x1B[0m + \x1B[1;31mThird line\x1B[0m + Fourth line + \x1B[3m<---- remaining lines omitted ---->\x1B[0m" )))) ) } @@ -1149,15 +1147,15 @@ mod tests { err(displays_as(contains_substring(indoc!( " Difference: - <---- remaining lines omitted ----> - -line - +Second line - Third line - -Foorth line - +Fourth line - -Fifth - +Fifth line - <---- remaining lines omitted ----> + \x1B[3m<---- remaining lines omitted ---->\x1B[0m + \x1B[1;34mline\x1B[0m + \x1B[1;31mSecond line\x1B[0m + Third line + \x1B[1;34mFoorth line\x1B[0m + \x1B[1;31mFourth line\x1B[0m + \x1B[1;34mFifth\x1B[0m + \x1B[1;31mFifth line\x1B[0m + \x1B[3m<---- remaining lines omitted ---->\x1B[0m " )))) ) @@ -1187,11 +1185,11 @@ mod tests { result, err(displays_as(contains_substring(indoc!( " - First line - +Second line - -Second lines - Third line - +Fourth line + First line + \x1B[1;31mSecond line\x1B[0m + \x1B[1;34mSecond lines\x1B[0m + Third line + \x1B[1;31mFourth line\x1B[0m " )))) )