cargo_tarpaulin/
path_utils.rs1use crate::config::Config;
2use std::env::var;
3use std::ffi::OsStr;
4use std::path::{Path, PathBuf};
5use walkdir::{DirEntry, WalkDir};
6
7pub 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
22pub fn is_profraw_file(entry: &DirEntry) -> bool {
24 let p = entry.path();
25 p.is_file() && p.extension() == Some(OsStr::new("profraw"))
26}
27
28pub fn is_source_file(entry: &DirEntry) -> bool {
30 let p = entry.path();
31 p.is_file() && p.extension() == Some(OsStr::new("rs"))
32}
33
34fn is_target_folder(entry: &Path, target: &Path) -> bool {
36 entry.starts_with(target)
37}
38
39fn 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
48fn 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 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 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}