about summary refs log tree commit diff
path: root/clippy_dev/src/bless.rs
blob: 19153aa74d0b8956dfdeb3c14fa7033cbd2ce448 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
//! `bless` updates the reference files in the repo with changed output files
//! from the last test run.

use std::ffi::OsStr;
use std::fs;
use std::lazy::SyncLazy;
use std::path::{Path, PathBuf};
use walkdir::WalkDir;

use crate::clippy_project_root;

static CLIPPY_BUILD_TIME: SyncLazy<Option<std::time::SystemTime>> = SyncLazy::new(|| {
    let mut path = std::env::current_exe().unwrap();
    path.set_file_name("cargo-clippy");
    fs::metadata(path).ok()?.modified().ok()
});

/// # Panics
///
/// Panics if the path to a test file is broken
pub fn bless(ignore_timestamp: bool) {
    let test_suite_dirs = [
        clippy_project_root().join("tests").join("ui"),
        clippy_project_root().join("tests").join("ui-internal"),
        clippy_project_root().join("tests").join("ui-toml"),
        clippy_project_root().join("tests").join("ui-cargo"),
    ];
    for test_suite_dir in &test_suite_dirs {
        WalkDir::new(test_suite_dir)
            .into_iter()
            .filter_map(Result::ok)
            .filter(|f| f.path().extension() == Some(OsStr::new("rs")))
            .for_each(|f| {
                let test_name = f.path().strip_prefix(test_suite_dir).unwrap();
                for &ext in &["stdout", "stderr", "fixed"] {
                    let test_name_ext = format!("stage-id.{}", ext);
                    update_reference_file(
                        f.path().with_extension(ext),
                        test_name.with_extension(test_name_ext),
                        ignore_timestamp,
                    );
                }
            });
    }
}

fn update_reference_file(reference_file_path: PathBuf, test_name: PathBuf, ignore_timestamp: bool) {
    let test_output_path = build_dir().join(test_name);
    let relative_reference_file_path = reference_file_path.strip_prefix(clippy_project_root()).unwrap();

    // If compiletest did not write any changes during the test run,
    // we don't have to update anything
    if !test_output_path.exists() {
        return;
    }

    // If the test output was not updated since the last clippy build, it may be outdated
    if !ignore_timestamp && !updated_since_clippy_build(&test_output_path).unwrap_or(true) {
        return;
    }

    let test_output_file = fs::read(&test_output_path).expect("Unable to read test output file");
    let reference_file = fs::read(&reference_file_path).unwrap_or_default();

    if test_output_file != reference_file {
        // If a test run caused an output file to change, update the reference file
        println!("updating {}", &relative_reference_file_path.display());
        fs::copy(test_output_path, &reference_file_path).expect("Could not update reference file");

        // We need to re-read the file now because it was potentially updated from copying
        let reference_file = fs::read(&reference_file_path).unwrap_or_default();

        if reference_file.is_empty() {
            // If we copied over an empty output file, we remove the now empty reference file
            println!("removing {}", &relative_reference_file_path.display());
            fs::remove_file(reference_file_path).expect("Could not remove reference file");
        }
    }
}

fn updated_since_clippy_build(path: &Path) -> Option<bool> {
    let clippy_build_time = (*CLIPPY_BUILD_TIME)?;
    let modified = fs::metadata(path).ok()?.modified().ok()?;
    Some(modified >= clippy_build_time)
}

fn build_dir() -> PathBuf {
    let profile = env::var("PROFILE").unwrap_or_else(|_| "debug".to_string());
    let mut path = PathBuf::new();
    path.push(CARGO_TARGET_DIR.clone());
    path.push(profile);
    path.push("test_build_base");
    path
}