testing_logger/lib.rs
1//! This crate supports testing and asserting that appropriate log messages
2//! from the `log` crate are generated during tests.
3//!
4//! Log events are captured in a thread_local variable so this module behaves correctly
5//! when tests are run multithreaded.
6//!
7//! All log levels are captured, but none are sent to any logging system. The
8//! test developer should use the `validate()` function in order to check
9//! the captured log messages.
10//!
11//! # Examples
12//! ```
13//! #[macro_use]
14//! extern crate log;
15//! use log::Level;
16//! extern crate testing_logger;
17//!
18//! # fn main() { test_something();}
19//! # /* Don't put #[test] in code when running doc tests but DO put it in the documentation
20//! #[test]
21//! # */
22//! fn test_something() {
23//! testing_logger::setup();
24//! warn!("Something went wrong with {}", 10);
25//! testing_logger::validate( |captured_logs| {
26//! assert_eq!(captured_logs.len(), 1);
27//! assert_eq!(captured_logs[0].body, "Something went wrong with 10");
28//! assert_eq!(captured_logs[0].level, Level::Warn);
29//! });
30//! }
31//! ```
32//! The target is also captured if you want to validate that.
33//! ```
34//!
35//! # #[macro_use]
36//! # extern crate log;
37//! # use log::Level;
38//! # extern crate testing_logger;
39//! # fn main() { test_target();}
40//! # /* Don't put #[test] in code when running doc tests but DO put it in the documentation
41//! #[test]
42//! # */
43//! fn test_target() {
44//! testing_logger::setup();
45//! log!(target: "documentation", Level::Trace, "targetted log message");
46//! testing_logger::validate( |captured_logs| {
47//! assert_eq!(captured_logs.len(), 1);
48//! assert_eq!(captured_logs[0].target, "documentation");
49//! assert_eq!(captured_logs[0].body, "targetted log message");
50//! assert_eq!(captured_logs[0].level, Level::Trace);
51//! });
52//! }
53//! ```
54
55extern crate log;
56use log::{Log, Record, Metadata, LevelFilter, Level};
57use std::cell::RefCell;
58use std::sync::{Once, ONCE_INIT};
59
60/// A captured call to the logging system. A `Vec` of these is passed
61/// to the closure supplied to the `validate()` function.
62pub struct CapturedLog {
63 /// The formatted log message.
64 pub body: String,
65 /// The level.
66 pub level: Level,
67 /// The target.
68 pub target: String
69}
70
71thread_local!(static LOG_RECORDS: RefCell<Vec<CapturedLog>> = RefCell::new(Vec::with_capacity(3)));
72
73struct TestingLogger {}
74
75impl Log for TestingLogger {
76
77 #[allow(unused_variables)]
78 fn enabled(&self, metadata: &Metadata) -> bool {
79 true // capture all log levels
80 }
81
82 fn log(& self, record: &Record) {
83 LOG_RECORDS.with( |records| {
84 let captured_record = CapturedLog {
85 body: format!("{}",record.args()),
86 level: record.level(),
87 target: record.target().to_string()
88 };
89 records.borrow_mut().push(captured_record);
90 });
91 }
92
93 fn flush(&self) {}
94
95}
96
97static FIRST_TEST: Once = ONCE_INIT;
98
99static TEST_LOGGER: TestingLogger = TestingLogger{};
100
101/// Prepare the `testing_logger` to capture log messages for a test.
102///
103/// Should be called from every test that calls `validate()`, before any calls to the logging system.
104/// This function will install an internal `TestingLogger` as the logger if not already done so, and initialise
105/// its thread local storage for a new test.
106pub fn setup() {
107 FIRST_TEST.call_once( || {
108 log::set_logger(&TEST_LOGGER).map(|()|
109 log::set_max_level(LevelFilter::Trace)).unwrap();
110 });
111 LOG_RECORDS.with( |records| {
112 records.borrow_mut().truncate(0);
113 });
114}
115
116/// Used to validate any captured log events.
117///
118/// the `asserter` closure can check the number, body, target and level
119/// of captured log events. As a side effect, the records are cleared.
120pub fn validate<F>(asserter: F) where F: Fn(&Vec<CapturedLog>) {
121 LOG_RECORDS.with( |records| {
122 asserter(&records.borrow());
123 records.borrow_mut().truncate(0);
124 });
125}