Skip to main content

cpp_linter/
git.rs

1//! This module was primarily used to parse diff blobs.
2//!
3//! Since migrating to git-bot-feedback crate, this module is now purely for regression testing.
4//! Any logic that parses diffs from 2 text blobs has been moved to git-bot-feedback.
5//! Some diff creation/parsing logic remains in clang_tools/mod.rs module using libgit2 API instead.
6
7#[cfg(test)]
8mod test {
9    use std::{
10        env::{self, current_dir, set_current_dir},
11        fs,
12        process::Command,
13    };
14
15    use tempfile::{TempDir, tempdir};
16
17    use crate::{cli::LinesChangedOnly, rest_client::RestClient};
18    use git_bot_feedback::FileFilter;
19
20    const TEST_REPO_URL: &str = "https://github.com/cpp-linter/cpp-linter";
21
22    // used to setup a testing stage
23    fn clone_repo(sha: Option<&str>, path: &str, patch_path: Option<&str>) {
24        let ok = Command::new("git")
25            .args(["clone", TEST_REPO_URL, path])
26            .status()
27            .expect("Failed to clone repo");
28        if !ok.success() {
29            panic!("Failed to clone repo");
30        }
31        if let Some(sha) = sha {
32            let ok = Command::new("git")
33                .args(["-c", "advice.detachedHead=false", "checkout", sha])
34                .current_dir(path)
35                .status()
36                .expect("Failed to checkout commit");
37            if !ok.success() {
38                panic!("Failed to checkout commit");
39            }
40        }
41        if let Some(patch) = patch_path {
42            let canonical_patch_path = fs::canonicalize(patch).unwrap();
43            let patch_path = canonical_patch_path
44                .to_str()
45                .unwrap()
46                // on Windows, canonical paths can have a prefix of "\\?\" which git does not recognize
47                .trim_start_matches("\\\\?\\");
48            let ok = Command::new("git")
49                .args(["apply", "--index", patch_path])
50                .current_dir(path)
51                .status()
52                .expect("Failed to apply patch and stage its changes");
53            if !ok.success() {
54                panic!("Failed to apply patch and stage its changes");
55            }
56            let ok = Command::new("git")
57                .args(["status", "-s"])
58                .current_dir(path)
59                .status()
60                .expect("Failed to get git status after applying patch");
61            if !ok.success() {
62                panic!("Failed to get git status after applying patch");
63            }
64        }
65    }
66
67    fn get_temp_dir() -> TempDir {
68        let tmp = tempdir().unwrap();
69        println!("Using temp folder at {:?}", tmp.path());
70        tmp
71    }
72
73    async fn checkout_cpp_linter_py_repo(
74        sha: &str,
75        extensions: &[&str],
76        tmp: &TempDir,
77        patch_path: Option<&str>,
78        ignore_staged: bool,
79    ) -> Vec<crate::common_fs::FileObj> {
80        clone_repo(
81            Some(sha),
82            tmp.path().as_os_str().to_str().unwrap(),
83            patch_path,
84        );
85        // avoid use of REST API when testing in CI
86        unsafe {
87            env::set_var("GITHUB_ACTIONS", "false");
88            env::set_var("CI", "false");
89        }
90        let rest_api_client = RestClient::new().unwrap();
91        let file_filter = FileFilter::new(&["target"], extensions, None);
92        set_current_dir(tmp).unwrap();
93        let base_diff = if ignore_staged {
94            Some("HEAD".to_string())
95        } else {
96            None
97        };
98        rest_api_client
99            .get_list_of_changed_files(
100                &file_filter,
101                &LinesChangedOnly::Off.into(),
102                &base_diff,
103                ignore_staged,
104            )
105            .await
106            .unwrap()
107    }
108
109    #[tokio::test]
110    async fn with_no_changed_sources() {
111        // commit with no modified C/C++ sources
112        let sha = "0c236809891000b16952576dc34de082d7a40bf3";
113        let cur_dir = current_dir().unwrap();
114        let tmp = get_temp_dir();
115        let extensions = ["cpp", "hpp"];
116        let files = checkout_cpp_linter_py_repo(sha, &extensions, &tmp, None, false).await;
117        println!("files = {:?}", files);
118        assert!(files.is_empty());
119        set_current_dir(cur_dir).unwrap(); // prep to delete temp_folder
120        drop(tmp); // delete temp_folder
121    }
122
123    #[tokio::test]
124    async fn with_changed_sources() {
125        // commit with modified C/C++ sources
126        let sha = "950ff0b690e1903797c303c5fc8d9f3b52f1d3c5";
127        let cur_dir = current_dir().unwrap();
128        let tmp = get_temp_dir();
129        let extensions = ["cpp", "hpp"];
130        let files = checkout_cpp_linter_py_repo(sha, &extensions, &tmp, None, false).await;
131        println!("files = {:?}", files);
132        assert!(files.len() >= 2);
133        for file in files {
134            assert!(
135                extensions.contains(
136                    &file
137                        .name
138                        .extension()
139                        .unwrap()
140                        .to_string_lossy()
141                        .to_string()
142                        .as_str()
143                )
144            );
145        }
146        set_current_dir(cur_dir).unwrap(); // prep to delete temp_folder
147        drop(tmp); // delete temp_folder
148    }
149
150    #[tokio::test]
151    async fn with_staged_changed_sources() {
152        // commit with no modified C/C++ sources
153        let sha = "0c236809891000b16952576dc34de082d7a40bf3";
154        let cur_dir = current_dir().unwrap();
155        let tmp = get_temp_dir();
156        let extensions = ["cpp", "hpp"];
157        let files = checkout_cpp_linter_py_repo(
158            sha,
159            &extensions,
160            &tmp,
161            Some("tests/git_status_test_assets/cpp-linter/cpp-linter/test_git_lib.patch"),
162            false,
163        )
164        .await;
165        println!("files = {:?}", files);
166        assert!(!files.is_empty());
167        for file in files {
168            assert!(
169                extensions.contains(
170                    &file
171                        .name
172                        .extension()
173                        .unwrap()
174                        .to_string_lossy()
175                        .to_string()
176                        .as_str()
177                )
178            );
179        }
180        set_current_dir(cur_dir).unwrap(); // prep to delete temp_folder
181        drop(tmp); // delete temp_folder
182    }
183
184    #[tokio::test]
185    async fn with_ignored_staged_changes() {
186        // commit with no modified C/C++ sources
187        let sha = "0c236809891000b16952576dc34de082d7a40bf3";
188        let cur_dir = current_dir().unwrap();
189        let tmp = get_temp_dir();
190        let extensions = ["cpp", "hpp"];
191        let files = checkout_cpp_linter_py_repo(
192            sha,
193            &extensions,
194            &tmp,
195            Some("tests/git_status_test_assets/cpp-linter/cpp-linter/test_git_lib.patch"),
196            true,
197        )
198        .await;
199        eprintln!("files: {files:?}");
200        assert!(files.is_empty());
201        set_current_dir(cur_dir).unwrap(); // prep to delete temp_folder
202        drop(tmp); // delete temp_folder
203    }
204}