1use log::{Level, LevelFilter, Log};
2use std::cell::{Ref, RefCell};
3use std::fmt;
4use std::sync::Once;
5
6pub struct LogEntry {
8 pub level: Level,
9 pub body: String,
10}
11
12impl<'a> From<&'a log::Record<'a>> for LogEntry {
13 fn from(record: &'a log::Record) -> Self {
14 Self {
15 level: record.level(),
16 body: fmt::format(*record.args()),
17 }
18 }
19}
20
21thread_local! {
22 static LOG_ENTRIES: RefCell<Vec<LogEntry>> = RefCell::new(Vec::<LogEntry>::new());
23}
24
25pub struct MockLogger;
27
28impl MockLogger {
29 pub fn entries<F>(f: F)
31 where
32 F: FnOnce(Ref<'_, Vec<LogEntry>>),
33 {
34 LOG_ENTRIES.with(|entries| {
35 f(entries.borrow());
36 });
37 }
38
39 pub fn map<F>(f: F)
41 where
42 F: Fn(usize, &LogEntry),
43 {
44 LOG_ENTRIES.with(|entries| {
45 for (i, entry) in entries.borrow().iter().enumerate() {
46 f(i, entry);
47 }
48 });
49 }
50
51 pub fn empty() {
53 LOG_ENTRIES.with(|entries| {
54 entries.borrow_mut().truncate(0);
55 });
56 }
57}
58
59impl Log for MockLogger {
60 fn enabled(&self, _metadata: &log::Metadata) -> bool {
61 true
62 }
63
64 fn flush(&self) {}
65
66 fn log(&self, record: &log::Record) {
67 LOG_ENTRIES.with(|entries| {
68 entries.borrow_mut().push(LogEntry::from(record));
69 });
70 }
71}
72
73static INITIALIZED: Once = Once::new();
74static INSTANCE: MockLogger = MockLogger {};
75
76pub fn init() {
79 INITIALIZED.call_once(|| {
80 log::set_logger(&INSTANCE).unwrap();
81 log::set_max_level(LevelFilter::Debug);
82 });
83 LOG_ENTRIES.with(|entries| {
84 entries.borrow_mut().truncate(0);
85 });
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91 use log::{debug, error, info, warn};
92
93 #[test]
94 fn test_entries() {
95 init();
96 debug!("one");
97 info!("two");
98 warn!("three");
99 error!("four");
100 MockLogger::entries(|entries| {
101 assert_eq!(entries.len(), 4);
102 assert_eq!(entries[0].level, Level::Debug);
103 assert_eq!(entries[0].body, "one".to_owned());
104 assert_eq!(entries[1].level, Level::Info);
105 assert_eq!(entries[1].body, "two".to_owned());
106 assert_eq!(entries[2].level, Level::Warn);
107 assert_eq!(entries[2].body, "three".to_owned());
108 assert_eq!(entries[3].level, Level::Error);
109 assert_eq!(entries[3].body, "four".to_owned());
110 });
111 }
112
113 #[test]
114 fn test_map() {
115 init();
116 debug!("entry {}", 1);
117 debug!("entry {}", 2);
118 MockLogger::map(|i, entry| match i {
119 0 => {
120 assert_eq!(entry.level, Level::Debug);
121 assert_eq!(entry.body, "entry 1".to_owned());
122 }
123 1 => {
124 assert_eq!(entry.level, Level::Debug);
125 assert_eq!(entry.body, "entry 2".to_owned());
126 }
127 _ => {
128 panic!("MockLogger has too many entries!");
129 }
130 });
131 }
132}