1use anyhow::{Result, bail};
4
5pub(crate) fn ensure_command(name: &str) -> Result<()> {
12 let found = std::env::var_os("PATH")
13 .map(|paths| {
14 std::env::split_paths(&paths).any(|dir| {
15 if dir.join(name).is_file() {
16 return true;
17 }
18 #[cfg(windows)]
19 if dir.join(format!("{name}.exe")).is_file() {
20 return true;
21 }
22 false
23 })
24 })
25 .unwrap_or(false);
26 if !found {
27 bail!("{name} is not installed or not on PATH");
28 }
29 Ok(())
30}
31
32#[cfg(test)]
33mod tests {
34 use super::*;
35 use crate::test_support::env_lock;
36
37 #[test]
38 fn ensure_command_fails_for_nonexistent_binary() {
39 let result = ensure_command("shine-definitely-not-a-real-binary-xyz123");
40 assert!(result.is_err());
41 }
42
43 #[tokio::test]
44 async fn ensure_command_succeeds_when_binary_on_path() {
45 let dir = crate::test_support::make_temp_dir("shine-proc").await;
46 let bin_path = dir.join("myfakebin");
47 tokio::fs::write(&bin_path, b"").await.unwrap();
48
49 let result = {
50 let _guard = env_lock();
51 let old_path = std::env::var_os("PATH");
52 unsafe { std::env::set_var("PATH", &dir) };
56
57 let result = ensure_command("myfakebin");
58
59 unsafe {
61 match old_path {
62 Some(value) => std::env::set_var("PATH", value),
63 None => std::env::remove_var("PATH"),
64 }
65 }
66 result
67 };
68
69 tokio::fs::remove_dir_all(&dir).await.unwrap();
70
71 assert!(result.is_ok());
72 }
73}