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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
//! This crate supports testing and asserting that appropriate log messages
//! from the `log` crate are generated during tests.
//!
//! Log events are captured in a thread_local variable so this module behaves correctly
//! when tests are run multithreaded.
//!
//! All log levels are captured, but none are sent to any logging system. The
//! test developer should use the `validate()` function in order to check
//! the captured log messages.
//!
//! # Examples
//! ```
//! #[macro_use]
//! extern crate log;
//! use log::Level;
//! extern crate testing_logger;
//!
//! # fn main() { test_something();}
//! # /* Don't put #[test] in code when running doc tests but DO put it in the documentation
//! #[test]
//! # */
//! fn test_something() {
//!     testing_logger::setup();
//!     warn!("Something went wrong with {}", 10);
//!     testing_logger::validate( |captured_logs| {
//!         assert_eq!(captured_logs.len(), 1);
//!         assert_eq!(captured_logs[0].body, "Something went wrong with 10");
//!         assert_eq!(captured_logs[0].level, Level::Warn);
//!     });
//! }
//! ```
//! The target is also captured if you want to validate that.
//! ```
//!
//! # #[macro_use]
//! # extern crate log;
//! # use log::Level;
//! # extern crate testing_logger;
//! # fn main() { test_target();}
//! # /* Don't put #[test] in code when running doc tests but DO put it in the documentation
//! #[test]
//! # */
//! fn test_target() {
//!     testing_logger::setup();
//!     log!(target: "documentation", Level::Trace, "targetted log message");
//!     testing_logger::validate( |captured_logs| {
//!         assert_eq!(captured_logs.len(), 1);
//!         assert_eq!(captured_logs[0].target, "documentation");
//!         assert_eq!(captured_logs[0].body, "targetted log message");
//!         assert_eq!(captured_logs[0].level, Level::Trace);
//!     });
//! }
//! ```

extern crate log;
use log::{Log, Record, Metadata, LevelFilter, Level};
use std::cell::RefCell;
use std::sync::{Once, ONCE_INIT};

/// A captured call to the logging system. A `Vec` of these is passed
/// to the closure supplied to the `validate()` function.
pub struct CapturedLog {
    /// The formatted log message.
    pub body: String,
    /// The level.
    pub level: Level,
    /// The target.
    pub target: String
}

thread_local!(static LOG_RECORDS: RefCell<Vec<CapturedLog>> = RefCell::new(Vec::with_capacity(3)));

struct TestingLogger {}

impl Log for TestingLogger {

    #[allow(unused_variables)]
    fn enabled(&self, metadata: &Metadata) -> bool {
        true // capture all log levels
    }

    fn log(& self, record: &Record) {
        LOG_RECORDS.with( |records| {
            let captured_record = CapturedLog {
                body: format!("{}",record.args()),
                level: record.level(),
                target: record.target().to_string()
            };
            records.borrow_mut().push(captured_record);
        });
    }

    fn flush(&self) {}

}

static FIRST_TEST: Once = ONCE_INIT;

static TEST_LOGGER: TestingLogger = TestingLogger{};

/// Prepare the `testing_logger` to capture log messages for a test.
///
/// Should be called from every test that calls `validate()`, before any calls to the logging system.
/// This function will install an internal `TestingLogger` as the logger if not already done so, and initialise
/// its thread local storage for a new test.
pub fn setup() {
    FIRST_TEST.call_once( || {
        log::set_logger(&TEST_LOGGER).map(|()|
        log::set_max_level(LevelFilter::Trace)).unwrap();
    });
    LOG_RECORDS.with( |records| {
        records.borrow_mut().truncate(0);
    });
}

/// Used to validate any captured log events.
///
/// the `asserter` closure can check the number, body, target and level
/// of captured log events. As a side effect, the records are cleared.
pub fn validate<F>(asserter: F)  where F: Fn(&Vec<CapturedLog>) {
    LOG_RECORDS.with( |records| {
        asserter(&records.borrow());
        records.borrow_mut().truncate(0);
    });
}