pub use crate::{
executable_file_name as file_name, executable_find_in_paths as find_in_paths,
executable_native_library_name as native_library_name,
executable_sibling_of_current_image as sibling_of_current_image,
executable_stem_matches as stem_matches,
executable_unlock_for_replacement as unlock_for_replacement, EXECUTABLE_EXTENSION,
};
pub fn current_image() -> std::io::Result<std::path::PathBuf> {
std::env::current_exe()
}
pub fn find_on_path(name: &std::ffi::OsStr) -> Option<std::path::PathBuf> {
let path = std::env::var_os("PATH")?;
let directories: Vec<_> = std::env::split_paths(&path).collect();
find_in_paths(name, &directories)
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::OsStr;
#[test]
fn the_current_image_is_a_real_file_its_siblings_resolve_against() {
let image = current_image().expect("a running process has an image");
assert!(image.is_file(), "{} is a file", image.display());
let bare = image
.file_stem()
.expect("image stem")
.to_string_lossy()
.into_owned();
assert_eq!(
sibling_of_current_image(&bare).as_deref(),
Some(&*image),
"siblings resolve against exactly the image this reports"
);
}
#[test]
fn file_name_applies_the_host_executable_extension() {
let named = file_name("running-process-daemon");
match EXECUTABLE_EXTENSION {
Some(extension) => {
assert_eq!(named, format!("running-process-daemon.{extension}"));
assert!(std::path::Path::new(&named).extension().is_some());
}
None => assert_eq!(named, "running-process-daemon"),
}
}
#[test]
fn the_running_image_is_found_beside_itself() {
let current = std::env::current_exe().expect("current image");
let bare = current
.file_stem()
.expect("image stem")
.to_string_lossy()
.into_owned();
assert_eq!(sibling_of_current_image(&bare).as_deref(), Some(&*current));
}
#[test]
fn an_absent_sibling_is_none_not_a_missing_path() {
assert!(sibling_of_current_image("rp-no-such-sibling-program").is_none());
}
#[test]
fn native_library_name_carries_a_host_extension() {
let name = native_library_name(OsStr::new("clang"));
assert!(std::path::Path::new(&name).extension().is_some());
}
#[test]
fn native_library_name_is_idempotent_when_already_extended() {
assert_eq!(
native_library_name(OsStr::new("clang.so")),
OsStr::new("clang.so")
);
}
#[test]
fn find_in_paths_locates_the_current_image_beside_itself() {
let image = std::env::current_exe().expect("current executable image");
let directory = image.parent().expect("image directory").to_path_buf();
let name = image.file_name().expect("image file name");
assert_eq!(find_in_paths(name, &[directory]), Some(image));
}
#[test]
fn find_in_paths_reports_absence_for_an_unknown_name() {
let directory = std::env::temp_dir();
assert_eq!(
find_in_paths(OsStr::new("rp-no-such-executable-xyz"), &[directory]),
None,
);
}
#[test]
fn find_on_path_reports_absence_for_an_unknown_name() {
assert!(find_on_path(OsStr::new("rp-no-such-executable-on-path-xyz")).is_none());
}
#[test]
fn stem_matches_the_expected_program_name() {
assert!(stem_matches(
OsStr::new("/usr/bin/running-process"),
"running-process"
));
assert!(!stem_matches(
OsStr::new("/usr/bin/other"),
"running-process"
));
}
#[test]
fn unlock_for_replacement_matches_host_locking_semantics() {
let dir = std::env::temp_dir().join(format!(
"rp-executable-unlock-{}-{:?}",
std::process::id(),
std::thread::current().id(),
));
std::fs::create_dir_all(&dir).expect("create scratch dir");
let image = dir.join(file_name("probe"));
std::fs::write(&image, b"image").expect("write image");
let relocated = unlock_for_replacement(&image).expect("unlock");
assert_eq!(relocated, cfg!(target_os = "windows"));
assert!(image.is_file(), "the original path must still be a file");
let _ = std::fs::remove_dir_all(&dir);
}
}