1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
//! Helper functions and macros that allow for easier testing of crates that use `tracing`.
//!
//! This crate should mainly be used through the [`#[traced_test]`](attr.traced_test.html) macro.
//!
//! ## Usage
//!
//! First, add a dependency on `tracing-test` in `Cargo.toml`:
//!
//! ```toml
//! tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
//! tracing = "0.1"
//! tracing-test = "0.1"
//! ```
//!
//! Then, annotate your test function with the `#[traced_test]` macro.
//!
//! ```rust
//! use tracing::{info, warn};
//! use tracing_test::traced_test;
//!
//! #[tokio::test]
//! #[traced_test]
//! async fn test_logs_are_captured() {
//!     // Local log
//!     info!("This is being logged on the info level");
//!
//!     // Log from a spawned task (which runs in a separate thread)
//!     tokio::spawn(async {
//!         warn!("This is being logged on the warn level from a spawned task");
//!     })
//!     .await
//!     .unwrap();
//!
//!     // Ensure that certain strings are or aren't logged
//!     assert!(logs_contain("logged on the info level"));
//!     assert!(logs_contain("logged on the warn level"));
//!     assert!(!logs_contain("logged on the error level"));
//!
//!     // Ensure that the string `logged` is logged exactly twice
//!     logs_assert(|lines: &[&str]| {
//!         match lines.iter().filter(|line| line.contains("logged")).count() {
//!             2 => Ok(()),
//!             n => Err(format!("Expected two matching logs, but found {}", n)),
//!         }
//!     });
//! }
//! ```
//!
//! Done! You can write assertions using one of two injected functions:
//!
//! - `logs_contain(&str) -> bool`: Use this within an `assert!` call to ensure
//!   that a certain string is (or isn't) logged anywhere in the logs.
//! - `logs_assert(f: impl Fn(&[&str]) -> Result<(), String>)`:  Run a function
//!   against the log lines. If the function returns an `Err`, panic. This can
//!   be used to run arbitrary assertion logic against the logs.
//!
//! Logs are written to stdout, so they are captured by the cargo test runner
//! by default, but printed if the test fails.
//!
//! Of course, you can also annotate regular non-async tests:
//!
//! ```rust
//! use tracing::info;
//! use tracing_test::traced_test;
//!
//! #[traced_test]
//! #[test]
//! fn plain_old_test() {
//!     assert!(!logs_contain("Logging from a non-async test"));
//!     info!("Logging from a non-async test");
//!     assert!(logs_contain("Logging from a non-async test"));
//!     assert!(!logs_contain("This was never logged"));
//! }
//! ```
//!
//! ## Rationale / Why You Need This
//!
//! Tracing allows you to set a default subscriber within a scope:
//!
//! ```rust
//! # let subscriber = tracing::Dispatch::new(tracing_subscriber::FmtSubscriber::new());
//! # let req = 123;
//! # fn get_response(fake_req: u8) {}
//! let response = tracing::dispatcher::with_default(&subscriber, || get_response(req));
//! ```
//!
//! This works fine, as long as no threads are involved. As soon as you use a
//! multi-threaded test runtime (e.g. the `#[tokio::test]` with the
//! `rt-multi-thread` feature) and spawn tasks, the tracing logs in those tasks
//! will not be captured by the subscriber.
//!
//! The macro provided in this crate registers a global default subscriber instead.
//! This subscriber contains a writer which logs into a global static in-memory buffer.
//!
//! At the beginning of every test, the macro injects span opening code. The span
//! uses the name of the test function (unless it's already taken, then a counter
//! is appended). This means that the logs from a test are prefixed with the test
//! name, which helps when debugging.
//!
//! Finally, a function called `logs_contain(value: &str)` is injected into every
//! annotated test. It filters the logs in the buffer to include only lines
//! containing ` {span_name}: ` and then searches the value in the matching log
//! lines. This can be used to assert that a message was logged during a test.

pub mod internal;
mod subscriber;

pub use tracing_test_macro::traced_test;