|
1 |
| -#![allow(clippy::doc_markdown)] |
2 |
| -//! Logging for the Integration Tests |
3 |
| -//! |
4 |
| -//! Tests should start their own logging. |
5 |
| -//! |
6 |
| -//! To find tests that do not start their own logging: |
7 |
| -//! |
8 |
| -//! ´´´ sh |
9 |
| -//! awk 'BEGIN{RS=""; FS="\n"} /#\[tokio::test\]\s*async\s+fn\s+\w+\s*\(\s*\)\s*\{[^}]*\}/ && !/#\[tokio::test\]\s*async\s+fn\s+\w+\s*\(\s*\)\s*\{[^}]*INIT\.call_once/' $(find . -name "*.rs") |
10 |
| -//! ´´´ |
11 |
| -//! |
12 |
| -
|
13 |
| -use std::sync::Once; |
| 1 | +//! Setup for logging in tests. |
| 2 | +use std::collections::VecDeque; |
| 3 | +use std::io; |
| 4 | +use std::sync::{Mutex, MutexGuard, Once, OnceLock}; |
14 | 5 |
|
| 6 | +use torrust_tracker::bootstrap::logging::TraceStyle; |
15 | 7 | use tracing::level_filters::LevelFilter;
|
| 8 | +use tracing_subscriber::fmt::MakeWriter; |
16 | 9 |
|
17 |
| -#[allow(dead_code)] |
18 |
| -pub static INIT: Once = Once::new(); |
| 10 | +static INIT: Once = Once::new(); |
| 11 | + |
| 12 | +/// A global buffer containing the latest lines captured from logs. |
| 13 | +#[doc(hidden)] |
| 14 | +pub fn captured_logs_buffer() -> &'static Mutex<CircularBuffer> { |
| 15 | + static CAPTURED_LOGS_GLOBAL_BUFFER: OnceLock<Mutex<CircularBuffer>> = OnceLock::new(); |
| 16 | + CAPTURED_LOGS_GLOBAL_BUFFER.get_or_init(|| Mutex::new(CircularBuffer::new(10000, 200))) |
| 17 | +} |
| 18 | + |
| 19 | +pub fn setup() { |
| 20 | + INIT.call_once(|| { |
| 21 | + tracing_init(LevelFilter::ERROR, &TraceStyle::Default); |
| 22 | + }); |
| 23 | +} |
| 24 | + |
| 25 | +fn tracing_init(level_filter: LevelFilter, style: &TraceStyle) { |
| 26 | + let mock_writer = LogCapturer::new(captured_logs_buffer()); |
19 | 27 |
|
20 |
| -#[allow(dead_code)] |
21 |
| -pub fn tracing_stderr_init(filter: LevelFilter) { |
22 | 28 | let builder = tracing_subscriber::fmt()
|
23 |
| - .with_max_level(filter) |
| 29 | + .with_max_level(level_filter) |
24 | 30 | .with_ansi(true)
|
25 |
| - .with_writer(std::io::stderr); |
| 31 | + .with_test_writer() |
| 32 | + .with_writer(mock_writer); |
26 | 33 |
|
27 |
| - builder.pretty().with_file(true).init(); |
| 34 | + let () = match style { |
| 35 | + TraceStyle::Default => builder.init(), |
| 36 | + TraceStyle::Pretty(display_filename) => builder.pretty().with_file(*display_filename).init(), |
| 37 | + TraceStyle::Compact => builder.compact().init(), |
| 38 | + TraceStyle::Json => builder.json().init(), |
| 39 | + }; |
28 | 40 |
|
29 | 41 | tracing::info!("Logging initialized");
|
30 | 42 | }
|
| 43 | + |
| 44 | +/// It returns true is there is a log line containing all the texts passed. |
| 45 | +/// |
| 46 | +/// # Panics |
| 47 | +/// |
| 48 | +/// Will panic if it can't get the lock for the global buffer or convert it into |
| 49 | +/// a vec. |
| 50 | +#[must_use] |
| 51 | +#[allow(dead_code)] |
| 52 | +pub fn logs_contains_a_line_with(texts: &[&str]) -> bool { |
| 53 | + // code-review: we can search directly in the buffer instead of converting |
| 54 | + // the buffer into a string but that would slow down the tests because |
| 55 | + // cloning should be faster that locking the buffer for searching. |
| 56 | + // Because the buffer is not big. |
| 57 | + let logs = String::from_utf8(captured_logs_buffer().lock().unwrap().as_vec()).unwrap(); |
| 58 | + |
| 59 | + for line in logs.split('\n') { |
| 60 | + if contains(line, texts) { |
| 61 | + return true; |
| 62 | + } |
| 63 | + } |
| 64 | + |
| 65 | + false |
| 66 | +} |
| 67 | + |
| 68 | +#[allow(dead_code)] |
| 69 | +fn contains(text: &str, texts: &[&str]) -> bool { |
| 70 | + texts.iter().all(|&word| text.contains(word)) |
| 71 | +} |
| 72 | + |
| 73 | +/// A tracing writer which captures the latests logs lines into a buffer. |
| 74 | +/// It's used to capture the logs in the tests. |
| 75 | +#[derive(Debug)] |
| 76 | +pub struct LogCapturer<'a> { |
| 77 | + logs: &'a Mutex<CircularBuffer>, |
| 78 | +} |
| 79 | + |
| 80 | +impl<'a> LogCapturer<'a> { |
| 81 | + pub fn new(buf: &'a Mutex<CircularBuffer>) -> Self { |
| 82 | + Self { logs: buf } |
| 83 | + } |
| 84 | + |
| 85 | + fn buf(&self) -> io::Result<MutexGuard<'a, CircularBuffer>> { |
| 86 | + self.logs.lock().map_err(|_| io::Error::from(io::ErrorKind::Other)) |
| 87 | + } |
| 88 | +} |
| 89 | + |
| 90 | +impl io::Write for LogCapturer<'_> { |
| 91 | + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { |
| 92 | + print!("{}", String::from_utf8(buf.to_vec()).unwrap()); |
| 93 | + |
| 94 | + let mut target = self.buf()?; |
| 95 | + |
| 96 | + target.write(buf) |
| 97 | + } |
| 98 | + |
| 99 | + fn flush(&mut self) -> io::Result<()> { |
| 100 | + self.buf()?.flush() |
| 101 | + } |
| 102 | +} |
| 103 | + |
| 104 | +impl MakeWriter<'_> for LogCapturer<'_> { |
| 105 | + type Writer = Self; |
| 106 | + |
| 107 | + fn make_writer(&self) -> Self::Writer { |
| 108 | + LogCapturer::new(self.logs) |
| 109 | + } |
| 110 | +} |
| 111 | + |
| 112 | +#[derive(Debug)] |
| 113 | +pub struct CircularBuffer { |
| 114 | + max_size: usize, |
| 115 | + buffer: VecDeque<u8>, |
| 116 | +} |
| 117 | + |
| 118 | +impl CircularBuffer { |
| 119 | + #[must_use] |
| 120 | + pub fn new(max_lines: usize, average_line_size: usize) -> Self { |
| 121 | + Self { |
| 122 | + max_size: max_lines * average_line_size, |
| 123 | + buffer: VecDeque::with_capacity(max_lines * average_line_size), |
| 124 | + } |
| 125 | + } |
| 126 | + |
| 127 | + /// # Errors |
| 128 | + /// |
| 129 | + /// Won't return any error. |
| 130 | + #[allow(clippy::unnecessary_wraps)] |
| 131 | + pub fn write(&mut self, buf: &[u8]) -> io::Result<usize> { |
| 132 | + for &byte in buf { |
| 133 | + if self.buffer.len() == self.max_size { |
| 134 | + // Remove oldest byte to make space |
| 135 | + self.buffer.pop_front(); |
| 136 | + } |
| 137 | + self.buffer.push_back(byte); |
| 138 | + } |
| 139 | + |
| 140 | + Ok(buf.len()) |
| 141 | + } |
| 142 | + |
| 143 | + /// # Errors |
| 144 | + /// |
| 145 | + /// Won't return any error. |
| 146 | + #[allow(clippy::unnecessary_wraps)] |
| 147 | + #[allow(clippy::unused_self)] |
| 148 | + pub fn flush(&mut self) -> io::Result<()> { |
| 149 | + Ok(()) |
| 150 | + } |
| 151 | + |
| 152 | + #[must_use] |
| 153 | + pub fn as_vec(&self) -> Vec<u8> { |
| 154 | + self.buffer.iter().copied().collect() |
| 155 | + } |
| 156 | +} |
0 commit comments