compiletest/runtest/
compute_diff.rs

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