diff --git a/crates/snapbox/src/cmd.rs b/crates/snapbox/src/cmd.rs index ac47322f..5591b54e 100644 --- a/crates/snapbox/src/cmd.rs +++ b/crates/snapbox/src/cmd.rs @@ -849,29 +849,90 @@ fn wait( #[doc(inline)] pub use crate::cargo_bin; -/// Look up the path to a cargo-built binary within an integration test. +/// Look up the path to a cargo-built binary within an integration test /// -/// **NOTE:** Prefer [`cargo_bin!`] as this makes assumptions about cargo +/// Cargo support: +/// - `>1.94`: works +/// - `>=1.91,<=1.93`: works with default `build-dir` +/// - `<=1.92`: works +/// +/// # Panic +/// +/// Panics if no binary is found pub fn cargo_bin(name: &str) -> std::path::PathBuf { - let env_var = format!("CARGO_BIN_EXE_{name}"); + cargo_bin_opt(name).unwrap_or_else(|| missing_cargo_bin(name)) +} + +/// Look up the path to a cargo-built binary within an integration test +/// +/// Returns `None` if the binary doesn't exist +/// +/// Cargo support: +/// - `>1.94`: works +/// - `>=1.91,<=1.93`: works with default `build-dir` +/// - `<=1.92`: works +pub fn cargo_bin_opt(name: &str) -> Option { + let env_var = format!("{CARGO_BIN_EXE_}{name}"); std::env::var_os(env_var) .map(|p| p.into()) - .unwrap_or_else(|| target_dir().join(format!("{}{}", name, std::env::consts::EXE_SUFFIX))) + .or_else(|| legacy_cargo_bin(name)) +} + +/// Return all the name and path for all binaries built by Cargo +/// +/// Cargo support: +/// - `>1.94`: works +pub fn cargo_bins() -> impl Iterator { + std::env::vars_os() + .filter_map(|(k, v)| { + k.into_string() + .ok() + .map(|k| (k, std::path::PathBuf::from(v))) + }) + .filter_map(|(k, v)| k.strip_prefix(CARGO_BIN_EXE_).map(|s| (s.to_owned(), v))) +} + +const CARGO_BIN_EXE_: &str = "CARGO_BIN_EXE_"; + +fn missing_cargo_bin(name: &str) -> ! { + let possible_names: Vec<_> = cargo_bins().map(|(k, _)| k).collect(); + if possible_names.is_empty() { + panic!("`CARGO_BIN_EXE_{name}` is unset +help: if this is running within a unit test, move it to an integration test to gain access to `CARGO_BIN_EXE_{name}`") + } else { + let mut names = String::new(); + for (i, name) in possible_names.iter().enumerate() { + use std::fmt::Write as _; + if i != 0 { + let _ = write!(&mut names, ", "); + } + let _ = write!(&mut names, "\"{name}\""); + } + panic!( + "`CARGO_BIN_EXE_{name}` is unset +help: available binary names are {names}" + ) + } +} + +fn legacy_cargo_bin(name: &str) -> Option { + let target_dir = target_dir()?; + let bin_path = target_dir.join(format!("{}{}", name, std::env::consts::EXE_SUFFIX)); + if !bin_path.exists() { + return None; + } + Some(bin_path) } // Adapted from // https://github.com/rust-lang/cargo/blob/485670b3983b52289a2f353d589c57fae2f60f82/tests/testsuite/support/mod.rs#L507 -fn target_dir() -> std::path::PathBuf { - std::env::current_exe() - .ok() - .map(|mut path| { - path.pop(); - if path.ends_with("deps") { - path.pop(); - } - path - }) - .unwrap() +fn target_dir() -> Option { + let mut path = std::env::current_exe().ok()?; + let _test_bin_name = path.pop(); + if path.ends_with("deps") { + let _deps = path.pop(); + } + Some(path) } #[cfg(feature = "examples")] @@ -1009,3 +1070,10 @@ pub(crate) mod examples { target.crate_types == ["bin"] && target.kind == ["example"] } } + +#[test] +#[should_panic = "`CARGO_BIN_EXE_non-existent` is unset +help: if this is running within a unit test, move it to an integration test to gain access to `CARGO_BIN_EXE_non-existent`"] +fn cargo_bin_in_unit_test() { + cargo_bin("non-existent"); +} diff --git a/crates/snapbox/tests/testsuite/cmd.rs b/crates/snapbox/tests/testsuite/cmd.rs index 6a81d7ce..98dcb092 100644 --- a/crates/snapbox/tests/testsuite/cmd.rs +++ b/crates/snapbox/tests/testsuite/cmd.rs @@ -33,3 +33,11 @@ fn large_stdout_single() { .assert() .success(); } + +#[test] +#[cfg(feature = "cmd")] +#[should_panic = "`CARGO_BIN_EXE_non-existent` is unset +help: available binary names are \"snap-fixture\""] +fn cargo_bin_non_existent() { + let _ = snapbox::cmd::cargo_bin("non-existent"); +} diff --git a/crates/trycmd/src/cargo.rs b/crates/trycmd/src/cargo.rs index ab979276..ce983efa 100644 --- a/crates/trycmd/src/cargo.rs +++ b/crates/trycmd/src/cargo.rs @@ -2,6 +2,10 @@ #[doc(inline)] pub use snapbox::cmd::cargo_bin; +#[doc(inline)] +pub use snapbox::cmd::cargo_bin_opt; +#[doc(inline)] +pub use snapbox::cmd::cargo_bins; /// Prepare an example for testing /// diff --git a/crates/trycmd/src/registry.rs b/crates/trycmd/src/registry.rs index 1a81ed66..135f4177 100644 --- a/crates/trycmd/src/registry.rs +++ b/crates/trycmd/src/registry.rs @@ -47,8 +47,7 @@ impl BinRegistry { } if self.fallback { - let path = crate::cargo::cargo_bin(name); - if path.exists() { + if let Some(path) = crate::cargo::cargo_bin_opt(name) { return crate::schema::Bin::Path(path); } }