compiletest/
compute_diff.rs

1use std::collections::VecDeque;
2use std::fs::{File, FileType};
3
4use camino::Utf8Path;
5
6#[derive(Debug, PartialEq)]
7pub enum DiffLine {
8    Context(String),
9    Expected(String),
10    Resulting(String),
11}
12
13#[derive(Debug, PartialEq)]
14pub struct Mismatch {
15    pub line_number: u32,
16    pub lines: Vec<DiffLine>,
17}
18
19impl Mismatch {
20    fn new(line_number: u32) -> Mismatch {
21        Mismatch { line_number, lines: Vec::new() }
22    }
23}
24
25// Produces a diff between the expected output and actual output.
26pub fn make_diff(expected: &str, actual: &str, context_size: usize) -> Vec<Mismatch> {
27    let mut line_number = 1;
28    let mut context_queue: VecDeque<&str> = VecDeque::with_capacity(context_size);
29    let mut lines_since_mismatch = context_size + 1;
30    let mut results = Vec::new();
31    let mut mismatch = Mismatch::new(0);
32
33    for result in diff::lines(expected, actual) {
34        match result {
35            diff::Result::Left(s) => {
36                if lines_since_mismatch >= context_size && lines_since_mismatch > 0 {
37                    results.push(mismatch);
38                    mismatch = Mismatch::new(line_number - context_queue.len() as u32);
39                }
40
41                while let Some(line) = context_queue.pop_front() {
42                    mismatch.lines.push(DiffLine::Context(line.to_owned()));
43                }
44
45                mismatch.lines.push(DiffLine::Expected(s.to_owned()));
46                line_number += 1;
47                lines_since_mismatch = 0;
48            }
49            diff::Result::Right(s) => {
50                if lines_since_mismatch >= context_size && lines_since_mismatch > 0 {
51                    results.push(mismatch);
52                    mismatch = Mismatch::new(line_number - context_queue.len() as u32);
53                }
54
55                while let Some(line) = context_queue.pop_front() {
56                    mismatch.lines.push(DiffLine::Context(line.to_owned()));
57                }
58
59                mismatch.lines.push(DiffLine::Resulting(s.to_owned()));
60                lines_since_mismatch = 0;
61            }
62            diff::Result::Both(s, _) => {
63                if context_queue.len() >= context_size {
64                    let _ = context_queue.pop_front();
65                }
66
67                if lines_since_mismatch < context_size {
68                    mismatch.lines.push(DiffLine::Context(s.to_owned()));
69                } else if context_size > 0 {
70                    context_queue.push_back(s);
71                }
72
73                line_number += 1;
74                lines_since_mismatch += 1;
75            }
76        }
77    }
78
79    results.push(mismatch);
80    results.remove(0);
81
82    results
83}
84
85pub(crate) fn write_diff(expected: &str, actual: &str, context_size: usize) -> String {
86    use std::fmt::Write;
87    let mut output = String::new();
88    let diff_results = make_diff(expected, actual, context_size);
89    for result in diff_results {
90        let mut line_number = result.line_number;
91        for line in result.lines {
92            match line {
93                DiffLine::Expected(e) => {
94                    writeln!(output, "-\t{}", e).unwrap();
95                    line_number += 1;
96                }
97                DiffLine::Context(c) => {
98                    writeln!(output, "{}\t{}", line_number, c).unwrap();
99                    line_number += 1;
100                }
101                DiffLine::Resulting(r) => {
102                    writeln!(output, "+\t{}", r).unwrap();
103                }
104            }
105        }
106        writeln!(output).unwrap();
107    }
108    output
109}
110
111/// Filters based on filetype and extension whether to diff a file.
112///
113/// Returns whether any data was actually written.
114pub(crate) fn write_filtered_diff<Filter>(
115    diff_filename: &str,
116    out_dir: &Utf8Path,
117    compare_dir: &Utf8Path,
118    verbose: bool,
119    filter: Filter,
120) -> bool
121where
122    Filter: Fn(FileType, Option<&str>) -> bool,
123{
124    use std::io::{Read, Write};
125    let mut diff_output = File::create(diff_filename).unwrap();
126    let mut wrote_data = false;
127    for entry in walkdir::WalkDir::new(out_dir.as_std_path()) {
128        let entry = entry.expect("failed to read file");
129        let extension = entry.path().extension().and_then(|p| p.to_str());
130        if filter(entry.file_type(), extension) {
131            let expected_path = compare_dir
132                .as_std_path()
133                .join(entry.path().strip_prefix(&out_dir.as_std_path()).unwrap());
134            let expected = if let Ok(s) = std::fs::read(&expected_path) { s } else { continue };
135            let actual_path = entry.path();
136            let actual = std::fs::read(&actual_path).unwrap();
137            let diff = unified_diff::diff(
138                &expected,
139                &expected_path.to_str().unwrap(),
140                &actual,
141                &actual_path.to_str().unwrap(),
142                3,
143            );
144            wrote_data |= !diff.is_empty();
145            diff_output.write_all(&diff).unwrap();
146        }
147    }
148
149    if !wrote_data {
150        println!("note: diff is identical to nightly rustdoc");
151        assert!(diff_output.metadata().unwrap().len() == 0);
152        return false;
153    } else if verbose {
154        eprintln!("printing diff:");
155        let mut buf = Vec::new();
156        diff_output.read_to_end(&mut buf).unwrap();
157        std::io::stderr().lock().write_all(&mut buf).unwrap();
158    }
159    true
160}