compiletest/runtest/
compute_diff.rs1use std::collections::VecDeque;
2
3#[derive(Debug, PartialEq)]
4pub(crate) enum DiffLine {
5 Context(String),
6 Expected(String),
7 Resulting(String),
8}
9
10#[derive(Debug, PartialEq)]
11pub(crate) struct Mismatch {
12 pub(crate) line_number: u32,
13 pub(crate) lines: Vec<DiffLine>,
14}
15
16impl Mismatch {
17 fn new(line_number: u32) -> Mismatch {
18 Mismatch { line_number, lines: Vec::new() }
19 }
20}
21
22pub(crate) fn make_diff(expected: &str, actual: &str, context_size: usize) -> Vec<Mismatch> {
24 let mut line_number = 1;
25 let mut context_queue: VecDeque<&str> = VecDeque::with_capacity(context_size);
26 let mut lines_since_mismatch = context_size + 1;
27 let mut results = Vec::new();
28 let mut mismatch = Mismatch::new(0);
29
30 for result in diff::lines(expected, actual) {
31 match result {
32 diff::Result::Left(s) => {
33 if lines_since_mismatch >= context_size && lines_since_mismatch > 0 {
34 results.push(mismatch);
35 mismatch = Mismatch::new(line_number - context_queue.len() as u32);
36 }
37
38 while let Some(line) = context_queue.pop_front() {
39 mismatch.lines.push(DiffLine::Context(line.to_owned()));
40 }
41
42 mismatch.lines.push(DiffLine::Expected(s.to_owned()));
43 line_number += 1;
44 lines_since_mismatch = 0;
45 }
46 diff::Result::Right(s) => {
47 if lines_since_mismatch >= context_size && lines_since_mismatch > 0 {
48 results.push(mismatch);
49 mismatch = Mismatch::new(line_number - context_queue.len() as u32);
50 }
51
52 while let Some(line) = context_queue.pop_front() {
53 mismatch.lines.push(DiffLine::Context(line.to_owned()));
54 }
55
56 mismatch.lines.push(DiffLine::Resulting(s.to_owned()));
57 lines_since_mismatch = 0;
58 }
59 diff::Result::Both(s, _) => {
60 if context_queue.len() >= context_size {
61 let _ = context_queue.pop_front();
62 }
63
64 if lines_since_mismatch < context_size {
65 mismatch.lines.push(DiffLine::Context(s.to_owned()));
66 } else if context_size > 0 {
67 context_queue.push_back(s);
68 }
69
70 line_number += 1;
71 lines_since_mismatch += 1;
72 }
73 }
74 }
75
76 results.push(mismatch);
77 results.remove(0);
78
79 results
80}
81
82pub(crate) fn write_diff(expected: &str, actual: &str, context_size: usize) -> String {
83 use std::fmt::Write;
84 let mut output = String::new();
85 let diff_results = make_diff(expected, actual, context_size);
86 for result in diff_results {
87 let mut line_number = result.line_number;
88 for line in result.lines {
89 match line {
90 DiffLine::Expected(e) => {
91 writeln!(output, "-\t{}", e).unwrap();
92 line_number += 1;
93 }
94 DiffLine::Context(c) => {
95 writeln!(output, "{}\t{}", line_number, c).unwrap();
96 line_number += 1;
97 }
98 DiffLine::Resulting(r) => {
99 writeln!(output, "+\t{}", r).unwrap();
100 }
101 }
102 }
103 writeln!(output).unwrap();
104 }
105 output
106}
107
108pub(crate) fn diff_by_lines(expected: &str, actual: &str) -> String {
109 use std::collections::HashMap;
110 use std::fmt::Write;
111 let mut output = String::new();
112 let mut expected_counts: HashMap<&str, usize> = HashMap::new();
113 let mut actual_counts: HashMap<&str, usize> = HashMap::new();
114
115 for line in expected.lines() {
116 *expected_counts.entry(line).or_insert(0) += 1;
117 }
118 for line in actual.lines() {
119 *actual_counts.entry(line).or_insert(0) += 1;
120 }
121
122 fn write_expected_only_lines(
123 output: &mut String,
124 expected_lines: &HashMap<&str, usize>,
125 actual_lines: &HashMap<&str, usize>,
126 ) {
127 let mut expected_only: Vec<(&str, usize)> = expected_lines
128 .iter()
129 .filter_map(|(&line, &expected_count)| {
130 let actual_count = actual_lines.get(line).copied().unwrap_or(0);
131 if expected_count > actual_count {
132 Some((line, expected_count - actual_count))
133 } else {
134 None
135 }
136 })
137 .collect();
138 expected_only.sort_by(|(a, _), (b, _)| a.cmp(b));
139
140 if expected_only.is_empty() {
141 writeln!(output, "(no lines found)").unwrap();
142 } else {
143 for (line, diff) in expected_only {
144 for _ in 0..diff {
145 writeln!(output, "{line}").unwrap();
146 }
147 }
148 }
149 }
150
151 writeln!(output, "Compare output by lines enabled, diff by lines:").unwrap();
152 writeln!(output, "Expected contains these lines that are not in actual:").unwrap();
153 write_expected_only_lines(&mut output, &expected_counts, &actual_counts);
154 writeln!(output, "Actual contains these lines that are not in expected:").unwrap();
155 write_expected_only_lines(&mut output, &actual_counts, &expected_counts);
156 output
157}