tokio_bin_process/lib.rs
1//! Allows your integration tests to run your application under a separate process and assert on [`tracing`](https://docs.rs/tracing) events.
2//!
3//! To achieve this, it locates or builds the application's executable,
4//! runs it with `tracing` in JSON mode,
5//! and then processes the JSON logs to both assert on and display in human readable form.
6//!
7//! It is a little opinionated and by default will fail the test when a `tracing` warning or error occurs.
8//! However a specific warning or error can be allowed on a per test basis.
9//!
10//! Example usage for an imaginary database project named cooldb:
11//!
12//! ```rust
13//! use tokio_bin_process::event::Level;
14//! use tokio_bin_process::{BinProcess, BinProcessBuilder, bin_path};
15//! use tokio_bin_process::event_matcher::EventMatcher;
16//! use std::time::Duration;
17//! use std::path::PathBuf;
18//! # // hack to make the doc test compile
19//! # macro_rules! bin_path {
20//! # ($bin_name:expr) => {
21//! # std::path::PathBuf::from("foo")
22//! # };
23//! # }
24//!
25//! /// you'll want a helper like this as you'll be creating this in every integration test.
26//! async fn cooldb_process() -> BinProcess {
27//! // start the process
28//! let mut process = BinProcessBuilder::from_path(
29//! // Locate the path to the cooldb binary from an integration test or benchmark
30//! bin_path!("cooldb")
31//! )
32//! .with_log_name(Some("cooldb1".to_owned())) // The name that BinProcess should prepend its forwarded logs with
33//! .with_env_vars(vec![
34//! // provide any custom env vars required
35//! ("FOO".to_owned(), "BAR".to_owned()),
36//! // tokio-bin-process relies on reading tracing json's output,
37//! // so configure the application to produce that
38//! ("COOLDB_LOG_FORMAT".to_owned(), "JSON".to_owned())
39//! ])
40//! .with_args(vec![
41//! // provide any custom CLI args required
42//! "--foo".to_owned(), "bar".to_owned(),
43//! // tokio-bin-process relies on reading tracing json's output,
44//! // so configure the application to produce that
45//! "--log-format".to_owned(), "json".to_owned()
46//! ])
47//! .start()
48//! .await;
49//!
50//! // block asynchrounously until the application gives an event indicating that its ready
51//! tokio::time::timeout(
52//! Duration::from_secs(30),
53//! process.wait_for(
54//! &EventMatcher::new()
55//! .with_level(Level::Info)
56//! .with_target("cooldb")
57//! .with_message("accepting inbound connections"),
58//! &[]
59//! ),
60//! )
61//! .await
62//! .unwrap();
63//! process
64//! }
65//!
66//! #[tokio::test]
67//! async fn test_some_functionality() {
68//! // start the db
69//! let cooldb = cooldb_process().await;
70//!
71//! // connect to the db, do something and assert we get the expected result
72//! perform_test();
73//!
74//! // Shutdown the DB, asserting that no warnings or errors occured,
75//! // but allow and expect a certain warning.
76//! // A drop bomb ensures that the test will fail if we forget to call this method.
77//! cooldb
78//! .shutdown_and_then_consume_events(&[
79//! EventMatcher::new()
80//! .with_level(Level::Warn)
81//! .with_target("cooldb::internal")
82//! .with_message("The user did something silly that we want to warn about but is actually expected in this test case")
83//! ])
84//! .await;
85//! }
86//! ```
87//!
88//! When Cargo builds integration tests or benchmarks it provides a path to the binary under test.
89//! We can make use of that for speed and robustness with [`BinProcessBuilder::from_path`].
90//!
91//! But that is not always flexible enough so as a fallback [`BinProcess`] can invoke Cargo again internally to ensure the binary we need is compiled via [`BinProcessBuilder::from_cargo_name`].
92pub mod event;
93pub mod event_matcher;
94mod process;
95
96pub use process::BinProcess;
97pub use process::BinProcessBuilder;
98
99/// When called from within an integration test or benchmark, returns the path to the binary with the specified crate name in the current package.
100///
101/// Whenever Cargo compiles a benchmark or integration test any binary crates in the same package will also be compiled.
102/// This macro returns the path to one of those compiled binaries.
103/// If no such binary exists then the macro will fail to compile.
104///
105/// For example:
106/// There is a test at `test/tests.rs` for a binary crate with `name="foo"` in its `Cargo.toml` and with a `src/bin/bar.rs`.
107/// Inside the test at `test/tests.rs`, both `bin_path!("foo")` and `bin_path!("bar")` would compile and return the path of their respective binaries.
108#[macro_export]
109macro_rules! bin_path {
110 ($bin_name:expr) => {
111 std::path::PathBuf::from(std::env!(concat!("CARGO_BIN_EXE_", $bin_name)))
112 };
113}