1extern crate which;
2
3#[cfg(all(unix, feature = "regex"))]
4use regex::Regex;
5use std::ffi::{OsStr, OsString};
6use std::fs;
7use std::io;
8use std::path::{Path, PathBuf};
9use std::{env, vec};
10use tempfile::TempDir;
11
12struct TestFixture {
13    /// Temp directory.
14    pub tempdir: TempDir,
15    /// $PATH
16    pub paths: OsString,
17    /// Binaries created in $PATH
18    pub bins: Vec<PathBuf>,
19}
20
21const SUBDIRS: &[&str] = &["a", "b", "c"];
22const BIN_NAME: &str = "bin";
23
24#[cfg(unix)]
25fn mk_bin(dir: &Path, path: &str, extension: &str) -> io::Result<PathBuf> {
26    use std::os::unix::fs::OpenOptionsExt;
27    let bin = dir.join(path).with_extension(extension);
28    fs::OpenOptions::new()
29        .write(true)
30        .create(true)
31        .mode(0o666 | (libc::S_IXUSR as u32))
32        .open(&bin)
33        .and_then(|_f| bin.canonicalize())
34}
35
36fn touch(dir: &Path, path: &str, extension: &str) -> io::Result<PathBuf> {
37    let b = dir.join(path).with_extension(extension);
38    fs::File::create(&b).and_then(|_f| b.canonicalize())
39}
40
41#[cfg(windows)]
42fn mk_bin(dir: &Path, path: &str, extension: &str) -> io::Result<PathBuf> {
43    touch(dir, path, extension)
44}
45
46impl TestFixture {
47    // tmp/a/bin
48    // tmp/a/bin.exe
49    // tmp/a/bin.cmd
50    // tmp/b/bin
51    // tmp/b/bin.exe
52    // tmp/b/bin.cmd
53    // tmp/c/bin
54    // tmp/c/bin.exe
55    // tmp/c/bin.cmd
56    pub fn new() -> TestFixture {
57        let tempdir = tempfile::tempdir().unwrap();
58        let mut builder = fs::DirBuilder::new();
59        builder.recursive(true);
60        let mut paths = vec![];
61        let mut bins = vec![];
62        for d in SUBDIRS.iter() {
63            let p = tempdir.path().join(d);
64            builder.create(&p).unwrap();
65            bins.push(mk_bin(&p, BIN_NAME, "").unwrap());
66            bins.push(mk_bin(&p, BIN_NAME, "exe").unwrap());
67            bins.push(mk_bin(&p, BIN_NAME, "cmd").unwrap());
68            paths.push(p);
69        }
70        let p = tempdir.path().join("win-bin");
71        builder.create(&p).unwrap();
72        bins.push(mk_bin(&p, "win-bin", "exe").unwrap());
73        paths.push(p);
74        TestFixture {
75            tempdir,
76            paths: env::join_paths(paths).unwrap(),
77            bins,
78        }
79    }
80
81    #[allow(dead_code)]
82    pub fn touch(&self, path: &str, extension: &str) -> io::Result<PathBuf> {
83        touch(self.tempdir.path(), path, extension)
84    }
85
86    pub fn mk_bin(&self, path: &str, extension: &str) -> io::Result<PathBuf> {
87        mk_bin(self.tempdir.path(), path, extension)
88    }
89}
90
91fn _which<T: AsRef<OsStr>>(f: &TestFixture, path: T) -> which::Result<which::CanonicalPath> {
92    which::CanonicalPath::new_in(path, Some(f.paths.clone()), f.tempdir.path())
93}
94
95fn _which_all<'a, T: AsRef<OsStr> + 'a>(
96    f: &'a TestFixture,
97    path: T,
98) -> which::Result<impl Iterator<Item = which::Result<which::CanonicalPath>> + '_> {
99    which::CanonicalPath::all_in(path, Some(f.paths.clone()), f.tempdir.path())
100}
101
102#[test]
103#[cfg(unix)]
104fn it_works() {
105    use std::process::Command;
106    let result = which::Path::new("rustc");
107    assert!(result.is_ok());
108
109    let which_result = Command::new("which").arg("rustc").output();
110
111    assert_eq!(
112        String::from(result.unwrap().to_str().unwrap()),
113        String::from_utf8(which_result.unwrap().stdout)
114            .unwrap()
115            .trim()
116    );
117}
118
119#[test]
120#[cfg(unix)]
121fn test_which() {
122    let f = TestFixture::new();
123    assert_eq!(_which(&f, &BIN_NAME).unwrap(), f.bins[0])
124}
125
126#[test]
127#[cfg(windows)]
128fn test_which() {
129    let f = TestFixture::new();
130    assert_eq!(_which(&f, &BIN_NAME).unwrap(), f.bins[1])
131}
132
133#[test]
134#[cfg(all(unix, feature = "regex"))]
135fn test_which_re_in_with_matches() {
136    let f = TestFixture::new();
137    f.mk_bin("a/bin_0", "").unwrap();
138    f.mk_bin("b/bin_1", "").unwrap();
139    let re = Regex::new(r"bin_\d").unwrap();
140
141    let result: Vec<PathBuf> = which::which_re_in(re, Some(f.paths))
142        .unwrap()
143        .into_iter()
144        .collect();
145
146    let temp = f.tempdir;
147
148    assert_eq!(
149        result,
150        vec![temp.path().join("a/bin_0"), temp.path().join("b/bin_1")]
151    )
152}
153
154#[test]
155#[cfg(all(unix, feature = "regex"))]
156fn test_which_re_in_without_matches() {
157    let f = TestFixture::new();
158    let re = Regex::new(r"bi[^n]").unwrap();
159
160    let result: Vec<PathBuf> = which::which_re_in(re, Some(f.paths))
161        .unwrap()
162        .into_iter()
163        .collect();
164
165    assert_eq!(result, Vec::<PathBuf>::new())
166}
167
168#[test]
169#[cfg(all(unix, feature = "regex"))]
170fn test_which_re_accepts_owned_and_borrow() {
171    which::which_re(Regex::new(r".").unwrap())
172        .unwrap()
173        .for_each(drop);
174    which::which_re(&Regex::new(r".").unwrap())
175        .unwrap()
176        .for_each(drop);
177    which::which_re_in(Regex::new(r".").unwrap(), Some("pth"))
178        .unwrap()
179        .for_each(drop);
180    which::which_re_in(&Regex::new(r".").unwrap(), Some("pth"))
181        .unwrap()
182        .for_each(drop);
183}
184
185#[test]
186#[cfg(unix)]
187fn test_which_extension() {
188    let f = TestFixture::new();
189    let b = Path::new(&BIN_NAME).with_extension("");
190    assert_eq!(_which(&f, &b).unwrap(), f.bins[0])
191}
192
193#[test]
194#[cfg(windows)]
195fn test_which_extension() {
196    let f = TestFixture::new();
197    let b = Path::new(&BIN_NAME).with_extension("cmd");
198    assert_eq!(_which(&f, &b).unwrap(), f.bins[2])
199}
200
201#[test]
202#[cfg(windows)]
203fn test_which_no_extension() {
204    let f = TestFixture::new();
205    let b = Path::new("win-bin");
206    let which_result = which::which_in(&b, Some(&f.paths), ".").unwrap();
207    // Make sure the extension is the correct case.
208    assert_eq!(which_result.extension(), f.bins[9].extension());
209    assert_eq!(fs::canonicalize(&which_result).unwrap(), f.bins[9])
210}
211
212#[test]
213fn test_which_not_found() {
214    let f = TestFixture::new();
215    assert!(_which(&f, "a").is_err());
216}
217
218#[test]
219fn test_which_second() {
220    let f = TestFixture::new();
221    let b = f.mk_bin("b/another", env::consts::EXE_EXTENSION).unwrap();
222    assert_eq!(_which(&f, "another").unwrap(), b);
223}
224
225#[test]
226fn test_which_all() {
227    let f = TestFixture::new();
228    let actual = _which_all(&f, BIN_NAME)
229        .unwrap()
230        .map(|c| c.unwrap())
231        .collect::<Vec<_>>();
232    let mut expected = f
233        .bins
234        .iter()
235        .map(|p| p.canonicalize().unwrap())
236        .collect::<Vec<_>>();
237    #[cfg(windows)]
238    {
239        expected.retain(|p| p.file_stem().unwrap() == BIN_NAME);
240        expected.retain(|p| p.extension().map(|ext| ext == "exe" || ext == "cmd") == Some(true));
241    }
242    #[cfg(not(windows))]
243    {
244        expected.retain(|p| p.file_name().unwrap() == BIN_NAME);
245    }
246    assert_eq!(actual, expected);
247}
248
249#[test]
250#[cfg(unix)]
251fn test_which_absolute() {
252    let f = TestFixture::new();
253    assert_eq!(
254        _which(&f, &f.bins[3]).unwrap(),
255        f.bins[3].canonicalize().unwrap()
256    );
257}
258
259#[test]
260#[cfg(windows)]
261fn test_which_absolute() {
262    let f = TestFixture::new();
263    assert_eq!(
264        _which(&f, &f.bins[4]).unwrap(),
265        f.bins[4].canonicalize().unwrap()
266    );
267}
268
269#[test]
270#[cfg(windows)]
271fn test_which_absolute_path_case() {
272    // Test that an absolute path with an uppercase extension
273    // is accepted.
274    let f = TestFixture::new();
275    let p = &f.bins[4];
276    assert_eq!(_which(&f, &p).unwrap(), f.bins[4].canonicalize().unwrap());
277}
278
279#[test]
280#[cfg(unix)]
281fn test_which_absolute_extension() {
282    let f = TestFixture::new();
283    // Don't append EXE_EXTENSION here.
284    let b = f.bins[3].parent().unwrap().join(&BIN_NAME);
285    assert_eq!(_which(&f, &b).unwrap(), f.bins[3].canonicalize().unwrap());
286}
287
288#[test]
289#[cfg(windows)]
290fn test_which_absolute_extension() {
291    let f = TestFixture::new();
292    // Don't append EXE_EXTENSION here.
293    let b = f.bins[4].parent().unwrap().join(&BIN_NAME);
294    assert_eq!(_which(&f, &b).unwrap(), f.bins[4].canonicalize().unwrap());
295}
296
297#[test]
298#[cfg(unix)]
299fn test_which_relative() {
300    let f = TestFixture::new();
301    assert_eq!(
302        _which(&f, "b/bin").unwrap(),
303        f.bins[3].canonicalize().unwrap()
304    );
305}
306
307#[test]
308#[cfg(windows)]
309fn test_which_relative() {
310    let f = TestFixture::new();
311    assert_eq!(
312        _which(&f, "b/bin").unwrap(),
313        f.bins[4].canonicalize().unwrap()
314    );
315}
316
317#[test]
318#[cfg(unix)]
319fn test_which_relative_extension() {
320    // test_which_relative tests a relative path without an extension,
321    // so test a relative path with an extension here.
322    let f = TestFixture::new();
323    let b = Path::new("b/bin").with_extension(env::consts::EXE_EXTENSION);
324    assert_eq!(_which(&f, &b).unwrap(), f.bins[3].canonicalize().unwrap());
325}
326
327#[test]
328#[cfg(windows)]
329fn test_which_relative_extension() {
330    // test_which_relative tests a relative path without an extension,
331    // so test a relative path with an extension here.
332    let f = TestFixture::new();
333    let b = Path::new("b/bin").with_extension("cmd");
334    assert_eq!(_which(&f, &b).unwrap(), f.bins[5].canonicalize().unwrap());
335}
336
337#[test]
338#[cfg(windows)]
339fn test_which_relative_extension_case() {
340    // Test that a relative path with an uppercase extension
341    // is accepted.
342    let f = TestFixture::new();
343    let b = Path::new("b/bin").with_extension("EXE");
344    assert_eq!(_which(&f, &b).unwrap(), f.bins[4].canonicalize().unwrap());
345}
346
347#[test]
348#[cfg(unix)]
349fn test_which_relative_leading_dot() {
350    let f = TestFixture::new();
351    assert_eq!(
352        _which(&f, "./b/bin").unwrap(),
353        f.bins[3].canonicalize().unwrap()
354    );
355}
356
357#[test]
358#[cfg(windows)]
359fn test_which_relative_leading_dot() {
360    let f = TestFixture::new();
361    assert_eq!(
362        _which(&f, "./b/bin").unwrap(),
363        f.bins[4].canonicalize().unwrap()
364    );
365}
366
367#[test]
368#[cfg(unix)]
369fn test_which_non_executable() {
370    // Shouldn't return non-executable files.
371    let f = TestFixture::new();
372    f.touch("b/another", "").unwrap();
373    assert!(_which(&f, "another").is_err());
374}
375
376#[test]
377#[cfg(unix)]
378fn test_which_absolute_non_executable() {
379    // Shouldn't return non-executable files, even if given an absolute path.
380    let f = TestFixture::new();
381    let b = f.touch("b/another", "").unwrap();
382    assert!(_which(&f, &b).is_err());
383}
384
385#[test]
386#[cfg(unix)]
387fn test_which_relative_non_executable() {
388    // Shouldn't return non-executable files.
389    let f = TestFixture::new();
390    f.touch("b/another", "").unwrap();
391    assert!(_which(&f, "b/another").is_err());
392}
393
394#[test]
395fn test_failure() {
396    let f = TestFixture::new();
397
398    let run = || -> which::Result<PathBuf> {
399        let p = _which(&f, "./b/bin")?;
400        Ok(p.into_path_buf())
401    };
402
403    let _ = run();
404}
405