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