Skip to main content

compiletest/runtest/
compute_diff.rs

1use 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
22// Produces a diff between the expected output and actual output.
23pub(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}