cargo-bin-file 0.1.0

Read binary paths from Cargo's CARGO_BIN_FILE_* environment variables
Documentation
use std::{env, path::PathBuf};

/// Returns the path stored in Cargo's `CARGO_BIN_FILE_*` environment
/// variables for the given binary crate name.
///
/// Cargo sets these variables for integration tests and benchmarks so one
/// package can locate another package's compiled binary by name.
///
/// The input crate name is normalized the same way Cargo does for these
/// variables: hyphens become underscores and ASCII letters are uppercased.
///
/// Returns `None` when the expected environment variable is not set.
///
/// # Examples
///
/// ```
/// use cargo_bin_file::bin_path;
///
/// let key = "CARGO_BIN_FILE_EXAMPLE_CRATE";
///
/// unsafe {
///     std::env::set_var(key, "/tmp/example-crate");
/// }
///
/// assert_eq!(
///     bin_path("example-crate"),
///     Some(std::path::PathBuf::from("/tmp/example-crate"))
/// );
///
/// unsafe {
///     std::env::remove_var(key);
/// }
/// ```
pub fn bin_path(name: &str) -> Option<PathBuf> {
    env::var(bin_file_env_var(name)).ok().map(Into::into)
}

fn bin_file_env_var(name: &str) -> String {
    format!("CARGO_BIN_FILE_{}", crate_env_name(name))
}

fn crate_env_name(name: &str) -> String {
    name.replace("-", "_").to_ascii_uppercase()
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::sync::{Mutex, OnceLock};

    fn env_lock() -> &'static Mutex<()> {
        static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
        LOCK.get_or_init(|| Mutex::new(()))
    }

    #[test]
    fn crate_name_is_normalized_for_env_var_suffix() {
        assert_eq!(crate_env_name("my-crate"), "MY_CRATE");
        assert_eq!(crate_env_name("already_UPPER"), "ALREADY_UPPER");
    }

    #[test]
    fn crate_name_maps_to_cargo_bin_file_env_var() {
        assert_eq!(
            bin_file_env_var("my-crate"),
            "CARGO_BIN_FILE_MY_CRATE".to_string()
        );
    }

    #[test]
    fn returns_none_when_env_var_is_missing() {
        let _guard = env_lock().lock().unwrap();
        let key = bin_file_env_var("missing-crate");

        unsafe {
            env::remove_var(&key);
        }

        assert_eq!(bin_path("missing-crate"), None);
    }

    #[test]
    fn returns_path_from_matching_env_var() {
        let _guard = env_lock().lock().unwrap();
        let key = bin_file_env_var("example-crate");
        let value = "/tmp/example-artifact";

        unsafe {
            env::set_var(&key, value);
        }

        assert_eq!(
            bin_path("example-crate"),
            Some(PathBuf::from(value))
        );

        unsafe {
            env::remove_var(&key);
        }
    }
}