diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index 6b5147cea6626..86ec8d52ac2e7 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -22,7 +22,7 @@ use crate::directives::{AuxCrate, TestProps}; use crate::errors::{Error, ErrorKind, load_errors}; use crate::output_capture::ConsoleOut; use crate::read2::{Truncated, read2_abbreviated}; -use crate::runtest::compute_diff::{DiffLine, make_diff, write_diff}; +use crate::runtest::compute_diff::{DiffLine, diff_by_lines, make_diff, write_diff}; use crate::util::{Utf8PathBufExt, add_dylib_path, static_regex}; use crate::{json, stamp_file_path}; @@ -2794,6 +2794,7 @@ impl<'test> TestCx<'test> { expected, actual, actual_unnormalized, + compare_output_by_lines || compare_output_by_lines_subset, ); } } else { @@ -2831,6 +2832,7 @@ impl<'test> TestCx<'test> { expected: &str, actual: &str, actual_unnormalized: &str, + show_diff_by_lines: bool, ) { writeln!(self.stderr, "diff of {stream}:\n"); if let Some(diff_command) = self.config.diff_command.as_deref() { @@ -2897,6 +2899,10 @@ impl<'test> TestCx<'test> { write_diff(&mismatches_unnormalized, &mismatches_normalized, 0) ); } + + if show_diff_by_lines { + write!(self.stderr, "{}", diff_by_lines(expected, actual)); + } } fn check_and_prune_duplicate_outputs( diff --git a/src/tools/compiletest/src/runtest/compute_diff.rs b/src/tools/compiletest/src/runtest/compute_diff.rs index 97ccdb989d437..a724d6a691764 100644 --- a/src/tools/compiletest/src/runtest/compute_diff.rs +++ b/src/tools/compiletest/src/runtest/compute_diff.rs @@ -104,3 +104,50 @@ pub(crate) fn write_diff(expected: &str, actual: &str, context_size: usize) -> S } output } + +pub(crate) fn diff_by_lines(expected: &str, actual: &str) -> String { + use std::collections::HashMap; + use std::fmt::Write; + let mut output = String::new(); + let mut expected_counts: HashMap<&str, usize> = HashMap::new(); + let mut actual_counts: HashMap<&str, usize> = HashMap::new(); + + for line in expected.lines() { + *expected_counts.entry(line).or_insert(0) += 1; + } + for line in actual.lines() { + *actual_counts.entry(line).or_insert(0) += 1; + } + + fn write_only_lines( + output: &mut String, + left_lines: &HashMap<&str, usize>, + right_lines: &HashMap<&str, usize>, + ) { + let mut left_only: Vec<(&str, usize)> = left_lines + .iter() + .filter_map(|(&line, &left_count)| { + let right_count = right_lines.get(line).copied().unwrap_or(0); + if left_count > right_count { Some((line, left_count - right_count)) } else { None } + }) + .collect(); + left_only.sort_by(|(a, _), (b, _)| a.cmp(b)); + + if left_only.is_empty() { + writeln!(output, "(no lines found)").unwrap(); + } else { + for (line, diff) in left_only { + for _ in 0..diff { + writeln!(output, "{line}").unwrap(); + } + } + } + } + + writeln!(output, "Compare output by lines enabled, diff by lines:").unwrap(); + writeln!(output, "Expected contains these lines that are not in actual:").unwrap(); + write_only_lines(&mut output, &expected_counts, &actual_counts); + writeln!(output, "Actual contains these lines that are not in expected:").unwrap(); + write_only_lines(&mut output, &actual_counts, &expected_counts); + output +}