compiletest/
compute_diff.rs

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