1#![doc = include_str!("../README.md")]
2#![warn(unused_crate_dependencies)]
3#![warn(clippy::pedantic)]
4
5mod file_state;
29mod path_part;
30mod path_with_state;
31mod program;
32mod suggest;
33mod which;
34
35pub use crate::which::Which;
37
38pub use crate::program::Program;
40
41#[cfg(test)]
42mod tests {
43 use super::*;
44 use crate::file_state::{file_state, FileState};
45 use crate::path_with_state::PathWithState;
46 use crate::which::Which;
47 use is_executable::IsExecutable;
48 use path_part::PartState;
49 use std::ffi::OsString;
50 use std::os::unix::fs::PermissionsExt;
51 use std::path::Path;
52
53 fn make_executable(file: &Path) {
54 let perms = std::fs::metadata(file).unwrap().permissions();
55 let mode = perms.mode() | 0o111;
56 std::fs::set_permissions(file, std::fs::Permissions::from_mode(mode)).unwrap();
57 }
58
59 #[test]
60 fn multiple_valid() {
61 let tmp_dir = tempfile::tempdir().unwrap();
62 let dir = tmp_dir.path().to_path_buf();
63 let file = dir.join("haha");
64
65 let tmp_dir_two = tempfile::tempdir().unwrap();
66 let dir_two = tmp_dir_two.path().to_path_buf();
67 let file_two = dir_two.join(file.file_name().unwrap());
68 let program = OsString::from(file.file_name().unwrap());
69
70 std::fs::write(&file, "contents").unwrap();
71 std::fs::write(&file_two, "contents").unwrap();
72 make_executable(&file);
73 make_executable(&file_two);
74
75 let program = Which {
76 program,
77 path_env: Some(vec![dir.as_os_str(), dir_two.as_os_str()].join(&OsString::from(":"))),
78 ..Which::default()
79 }
80 .diagnose()
81 .unwrap();
82
83 assert_eq!(
84 vec![
85 PathWithState {
86 path: file,
87 state: FileState::Valid,
88 },
89 PathWithState {
90 path: file_two,
91 state: FileState::Valid
92 }
93 ],
94 program.found_files
95 );
96 }
97
98 #[test]
99 fn check_executable_file() {
100 let tmp_dir = tempfile::tempdir().unwrap();
101 let dir = tmp_dir.path();
102 let file = dir.join("lol");
103 let name = OsString::from(file.file_name().unwrap());
104
105 std::fs::write(&file, "contents").unwrap();
106
107 let program = Which {
108 program: name.clone(),
109 path_env: Some(dir.as_os_str().into()),
110 ..Which::default()
111 }
112 .diagnose()
113 .unwrap();
114
115 assert_eq!(
116 vec![PathWithState {
117 path: file.clone(),
118 state: FileState::NotExecutable
119 }],
120 program.found_files
121 );
122
123 let perms = std::fs::metadata(&file).unwrap().permissions();
124 let mode = perms.mode() | 0o111;
125 std::fs::set_permissions(&file, std::fs::Permissions::from_mode(mode)).unwrap();
126
127 assert!(file.is_executable());
128
129 let program = Which {
130 program: name,
131 path_env: Some(dir.as_os_str().into()),
132 ..Which::default()
133 }
134 .diagnose()
135 .unwrap();
136
137 assert_eq!(
138 vec![PathWithState {
139 path: file,
140 state: FileState::Valid
141 }],
142 program.found_files
143 );
144 }
145
146 #[test]
147 fn check_symlink_file() {
148 let tmp_dir = tempfile::tempdir().unwrap();
149 let dir = tmp_dir.path();
150 let file = dir.join("lol");
151 let name = OsString::from(file.file_name().unwrap());
152
153 std::os::unix::fs::symlink(dir.join("nope"), &file).unwrap();
154
155 assert_eq!(FileState::BadSymlink, file_state(&file));
156
157 let program = Which {
158 program: name,
159 path_env: Some(dir.as_os_str().into()),
160 ..Which::default()
161 }
162 .diagnose()
163 .unwrap();
164
165 assert_eq!(
166 vec![PathWithState {
167 path: file,
168 state: FileState::BadSymlink
169 }],
170 program.found_files
171 );
172 }
173
174 #[test]
175 fn check_parts_are_dirs() {
176 let tmp_dir = tempfile::tempdir().unwrap();
177 let dir = tmp_dir.path();
178 let file = dir.join("lol");
179 let name = OsString::from(file.file_name().unwrap());
180 let expected = dir.join("nope");
181
182 std::fs::write(&expected, "lol").unwrap();
183 let program = Which {
184 program: name,
185 path_env: Some(vec![expected.as_os_str(), dir.as_os_str()].join(&OsString::from(":"))),
186 ..Which::default()
187 }
188 .diagnose()
189 .unwrap();
190
191 assert!(!program
192 .path_parts
193 .iter()
194 .any(|p| p.state == PartState::Missing));
195 assert!(program
196 .path_parts
197 .iter()
198 .any(|p| p.state == PartState::NotDir));
199 }
200
201 #[test]
202 fn check_path_parts_exist() {
203 let tmp_dir = tempfile::tempdir().unwrap();
204 let dir = tmp_dir.path();
205 let file = dir.join("lol");
206 let name = OsString::from(file.file_name().unwrap());
207 let expected = dir.join("nope");
208
209 let program = Which {
210 program: name,
211 path_env: Some(vec![expected.as_os_str(), dir.as_os_str()].join(&OsString::from(":"))),
212 ..Which::default()
213 }
214 .diagnose()
215 .unwrap();
216
217 assert!(program
218 .path_parts
219 .iter()
220 .any(|p| p.state == PartState::Missing));
221 assert!(!program
222 .path_parts
223 .iter()
224 .any(|p| p.state == PartState::NotDir));
225 }
226
227 #[test]
228 fn check_suggested_spelling() {
229 let tmp_dir = tempfile::tempdir().unwrap();
230 let dir = tmp_dir.path();
231 let file = dir.join("lol");
232 let name = OsString::from(file.file_name().unwrap());
233
234 let actual = dir.join("rofl");
235 std::fs::write(&actual, "contents").unwrap();
236 make_executable(&actual);
237
238 let program = Which {
239 program: name,
240 path_env: Some(dir.as_os_str().into()),
241 ..Which::default()
242 }
243 .diagnose()
244 .unwrap();
245
246 assert_eq!(
247 program.suggested.clone().unwrap(),
248 vec![actual.file_name().unwrap()]
249 );
250
251 assert_eq!(program.name, file.file_name().unwrap());
252 }
253}