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    // Filter out padded empty code lines
116    for line in expected.lines().filter(|l| l.trim() != "|") {
117        *expected_counts.entry(line).or_insert(0) += 1;
118    }
119    for line in actual.lines().filter(|l| l.trim() != "|") {
120        *actual_counts.entry(line).or_insert(0) += 1;
121    }
122
123    fn write_expected_only_lines(
124        output: &mut String,
125        expected_lines: &HashMap<&str, usize>,
126        actual_lines: &HashMap<&str, usize>,
127    ) {
128        let mut expected_only: Vec<(&str, usize)> = expected_lines
129            .iter()
130            .filter_map(|(&line, &expected_count)| {
131                let actual_count = actual_lines.get(line).copied().unwrap_or(0);
132                if expected_count > actual_count {
133                    Some((line, expected_count - actual_count))
134                } else {
135                    None
136                }
137            })
138            .collect();
139        expected_only.sort_by(|(a, _), (b, _)| a.cmp(b));
140
141        if expected_only.is_empty() {
142            writeln!(output, "(no lines found)").unwrap();
143        } else {
144            for (line, diff) in expected_only {
145                for _ in 0..diff {
146                    writeln!(output, "{line}").unwrap();
147                }
148            }
149        }
150    }
151
152    writeln!(output, "Compare output by lines enabled, diff by lines:").unwrap();
153    writeln!(output, "Expected contains these lines that are not in actual:").unwrap();
154    write_expected_only_lines(&mut output, &expected_counts, &actual_counts);
155    writeln!(output, "Actual contains these lines that are not in expected:").unwrap();
156    write_expected_only_lines(&mut output, &actual_counts, &expected_counts);
157    output
158}