cargo_tarpaulin/
path_utils.rs

1use crate::config::Config;
2use std::env::var;
3use std::ffi::OsStr;
4use std::path::{Path, PathBuf};
5use walkdir::{DirEntry, WalkDir};
6
7/// On windows removes the `\\?\\` prefix to UNC paths. For other operation systems just turns the
8/// `Path` into a `PathBuf`
9pub fn fix_unc_path(res: &Path) -> PathBuf {
10    if cfg!(windows) {
11        let res_str = res.display().to_string();
12        if res_str.starts_with(r#"\\?"#) {
13            PathBuf::from(res_str.replace(r#"\\?\"#, ""))
14        } else {
15            res.to_path_buf()
16        }
17    } else {
18        res.to_path_buf()
19    }
20}
21
22/// Returns true if the file is a rust source file
23pub fn is_profraw_file(entry: &DirEntry) -> bool {
24    let p = entry.path();
25    p.is_file() && p.extension() == Some(OsStr::new("profraw"))
26}
27
28/// Returns true if the file is a rust source file
29pub fn is_source_file(entry: &DirEntry) -> bool {
30    let p = entry.path();
31    p.is_file() && p.extension() == Some(OsStr::new("rs"))
32}
33
34/// Returns true if the folder is a target folder
35fn is_target_folder(entry: &Path, target: &Path) -> bool {
36    entry.starts_with(target)
37}
38
39/// Returns true if the file or folder is hidden
40fn is_hidden(entry: &Path, root: &Path) -> bool {
41    let check_hidden = |e: &Path| e.iter().any(|x| x.to_string_lossy().starts_with('.'));
42    match entry.strip_prefix(root) {
43        Ok(e) => check_hidden(e),
44        Err(_) => check_hidden(entry),
45    }
46}
47
48/// If `CARGO_HOME` is set filters out all folders within `CARGO_HOME`
49fn is_cargo_home(entry: &Path, root: &Path) -> bool {
50    match var("CARGO_HOME") {
51        Ok(s) => {
52            let path = Path::new(&s);
53            if path.is_absolute() && entry.starts_with(path) {
54                true
55            } else {
56                let home = root.join(path);
57                entry.starts_with(home)
58            }
59        }
60        _ => false,
61    }
62}
63
64fn is_part_of_project(e: &Path, root: &Path) -> bool {
65    if e.is_absolute() && root.is_absolute() {
66        e.starts_with(root)
67    } else if root.is_absolute() {
68        root.join(e).is_file()
69    } else {
70        // they're both relative and this isn't hit a lot - only really with FFI code
71        true
72    }
73}
74
75pub fn is_coverable_file_path(
76    path: impl AsRef<Path>,
77    root: impl AsRef<Path>,
78    target: impl AsRef<Path>,
79) -> bool {
80    let e = path.as_ref();
81    let ignorable_paths = !(is_target_folder(e, target.as_ref())
82        || is_hidden(e, root.as_ref())
83        || is_cargo_home(e, root.as_ref()));
84
85    ignorable_paths && is_part_of_project(e, root.as_ref())
86}
87
88pub fn get_source_walker(config: &Config) -> impl Iterator<Item = DirEntry> + '_ {
89    let root = config.root();
90    let target = config.target_dir();
91
92    let walker = WalkDir::new(&root).into_iter();
93    walker
94        .filter_entry(move |e| is_coverable_file_path(e.path(), &root, &target))
95        .filter_map(Result::ok)
96        .filter(move |e| !(config.exclude_path(e.path())))
97        .filter(move |e| config.include_path(e.path()))
98        .filter(is_source_file)
99}
100
101pub fn get_profile_walker(config: &Config) -> impl Iterator<Item = DirEntry> {
102    let walker = WalkDir::new(config.profraw_dir()).into_iter();
103    walker.filter_map(Result::ok).filter(is_profraw_file)
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109
110    #[test]
111    #[cfg(unix)]
112    fn system_headers_not_coverable() {
113        assert!(!is_coverable_file_path(
114            "/usr/include/c++/9/iostream",
115            "/home/ferris/rust/project",
116            "/home/ferris/rust/project/target"
117        ));
118    }
119
120    #[test]
121    #[cfg(windows)]
122    fn system_headers_not_coverable() {
123        assert!(!is_coverable_file_path(
124            "C:/Program Files/Visual Studio/include/c++/9/iostream",
125            "C:/User/ferris/rust/project",
126            "C:/User/ferris/rust/project/target"
127        ));
128    }
129
130    #[test]
131    fn basic_coverable_checks() {
132        assert!(is_coverable_file_path(
133            "/foo/src/lib.rs",
134            "/foo",
135            "/foo/target"
136        ));
137        assert!(!is_coverable_file_path(
138            "/foo/target/lib.rs",
139            "/foo",
140            "/foo/target"
141        ));
142    }
143
144    #[test]
145    fn is_hidden_check() {
146        // From issue#682
147        let hidden_root = Path::new("/home/.jenkins/project/");
148        let visible_root = Path::new("/home/jenkins/project/");
149
150        let hidden_file = Path::new(".cargo/src/hello.rs");
151        let visible_file = Path::new("src/hello.rs");
152
153        assert!(is_hidden(&hidden_root.join(hidden_file), hidden_root));
154        assert!(is_hidden(&visible_root.join(hidden_file), visible_root));
155
156        assert!(!is_hidden(&hidden_root.join(visible_file), hidden_root));
157        assert!(!is_hidden(&visible_root.join(visible_file), visible_root));
158    }
159}