test_bin/lib.rs
1//! A module for getting the crate binary in an integration test.
2//!
3//! If you are writing a command-line interface app then it is useful to write
4//! an integration test that uses the binary. You most likely want to launch the
5//! binary and inspect the output. This module lets you get the binary so it can
6//! be tested.
7//!
8//! # Examples
9//!
10//! basic usage:
11//!
12//! ```ignore
13//! let output = test_bin::get_test_bin("my_cli_app")
14//! .output()
15//! .expect("Failed to start my_cli_app");
16//! assert_eq!(
17//! String::from_utf8_lossy(&output.stdout),
18//! "Output from my CLI app!\n"
19//! );
20//! ```
21//!
22//! Refer to the [`std::process::Command` documentation](https://doc.rust-lang.org/std/process/struct.Command.html)
23//! for how to pass arguments, check exit status and more.
24//!
25//! NOTE: The `get_test_bin` function was deprecated in version 0.5.0 in favor
26//! of the `get_test_bin!` macro and then undeprecated in version 0.6.0. The
27//! macro was added because there was talk of changing cargo so the function
28//! wouldn't work. Fortunately, cargo was changed so that the macro wasn't
29//! needed. The `get_test_bin` function is the recommended way of using
30//! this crate. See [Cargo issue 14125](https://github.com/rust-lang/cargo/issues/14125).
31//!
32//! The `get_test_bin!` macro uses the `CARGO_BIN_EXE_<name>` environment
33//! variable which was introduced in [Rust 1.43 released on 23 April 2020](https://releases.rs/docs/1.43.0/).
34//!
35
36/// Returns the crate's binary as a `Command` that can be used for integration
37/// tests.
38///
39/// # Arguments
40///
41/// * `bin_name` - The name of the binary you want to test.
42///
43/// # Remarks
44///
45/// It panics on error. This is by design so the test that uses it fails.
46pub fn get_test_bin(bin_name: &str) -> std::process::Command {
47 let mut path = std::path::PathBuf::new();
48 // Try using the new CARGO_BIN_EXE_ environment variable.
49 let bin_path_key = format!("CARGO_BIN_EXE_{}", bin_name);
50 if let Ok(bin_path) = std::env::var(bin_path_key) {
51 path.push(bin_path);
52 } else {
53 // Use the legacy fallback method of finding the path.
54 // Create full path to binary.
55 path = get_test_bin_dir_fallback();
56 path.push(bin_name);
57 path.set_extension(std::env::consts::EXE_EXTENSION);
58
59 if !path.exists() {
60 // Print all environment variables.
61 for (key, value) in std::env::vars() {
62 println!("{key}: {value}");
63 }
64 let path: &'static str = env!("PATH");
65 println!("the $PATH variable at the time of compiling was: {path}");
66 }
67 }
68
69 assert!(path.exists());
70
71 // Create command
72 std::process::Command::new(path.into_os_string())
73}
74
75/// Returns the directory of the crate's binary.
76///
77/// # Remarks
78///
79/// It panics on error. This is by design so the test that uses it fails.
80fn get_test_bin_dir_fallback() -> std::path::PathBuf {
81 // Cargo puts the integration test binary in target/debug/deps
82 let current_exe =
83 std::env::current_exe().expect("Failed to get the path of the integration test binary");
84 let current_dir = current_exe
85 .parent()
86 .expect("Failed to get the directory of the integration test binary");
87
88 let test_bin_dir = current_dir
89 .parent()
90 .expect("Failed to get the binary folder");
91 test_bin_dir.to_owned()
92}
93
94/// Returns the crate's binary as a `Command` that can be used for integration
95/// tests.
96///
97/// # Arguments
98///
99/// * `bin_name` - The name of the binary you want to test. It must be a string literal.
100///
101/// # Remarks
102///
103/// It will fail to compile if the `bin_name` is incorrect. The `bin_name` is
104/// used for creating an environment variable.
105///
106/// If you want to not use a string literal every time then you can define a
107/// macro that returns a string literal:
108/// ```rust
109/// macro_rules! my_cli_app {
110/// () => ( "my_cli_app" )
111/// }
112/// ```
113/// And then use `my_cli_app!()` as the argument.
114#[macro_export]
115macro_rules! get_test_bin {
116 ($x:expr) => {
117 {
118 // Get path string. See the CARGO_BIN_EXE_<name> documentation:
119 // https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates
120 let path_str = env!(concat!("CARGO_BIN_EXE_", $x));
121 // Create command
122 std::process::Command::new(path_str)
123 }
124 };
125}