diff options
Diffstat (limited to 'src/logger.rs')
| -rw-r--r-- | src/logger.rs | 119 |
1 files changed, 109 insertions, 10 deletions
diff --git a/src/logger.rs b/src/logger.rs index 0352d61..0a0bdc2 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -1,5 +1,10 @@ //! Minimal stderr logging utilities. +use std::{ + cell::RefCell, + io::{self, Write}, +}; + /// Logging verbosity level. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum Verbosity { @@ -12,36 +17,130 @@ pub enum Verbosity { } /// Lightweight logger used by renderers and generators. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct Logger { +#[derive(Debug)] +pub struct Logger<W: Write> { verbosity: Verbosity, + writer: RefCell<W>, } -impl Logger { +impl Logger<io::Stderr> { /// Creates a logger with the given verbosity. pub fn new(verbosity: Verbosity) -> Self { - Self { verbosity } + Self { + verbosity, + writer: RefCell::new(io::stderr()), + } + } +} + +impl<W: Write> Logger<W> { + pub fn with_writer(verbosity: Verbosity, writer: W) -> Self { + Self { + verbosity, + writer: RefCell::new(writer), + } } /// Emits a warning unless verbosity is [`Verbosity::Quiet`]. pub fn warn(&self, message: impl AsRef<str>) { if self.verbosity != Verbosity::Quiet { - eprintln!("Warning: {}", message.as_ref()); + let _ = writeln!(self.writer.borrow_mut(), "Warning: {}", message.as_ref()); } } /// Emits an informational message only when verbosity is [`Verbosity::Verbose`]. pub fn info(&self, message: impl AsRef<str>) { if self.verbosity == Verbosity::Verbose { - eprintln!("Info: {}", message.as_ref()); + let _ = writeln!(self.writer.borrow_mut(), "Info: {}", message.as_ref()); } } } -impl Default for Logger { - fn default() -> Self { - Self { - verbosity: Verbosity::Quiet, +#[cfg(test)] +mod tests { + use std::io::{self, Write}; + + use super::{Logger, Verbosity}; + + fn logged_output(logger: &Logger<Vec<u8>>) -> String { + String::from_utf8(logger.writer.borrow().clone()).unwrap() + } + + #[test] + fn quiet_suppresses_warnings() { + let logger = Logger::with_writer(Verbosity::Quiet, Vec::new()); + logger.warn("skipped file"); + assert_eq!(logged_output(&logger), ""); + } + + #[test] + fn quiet_suppresses_info_messages() { + let logger = Logger::with_writer(Verbosity::Quiet, Vec::new()); + logger.info("rendering file"); + assert_eq!(logged_output(&logger), ""); + } + + #[test] + fn normal_writes_warnings() { + let logger = Logger::with_writer(Verbosity::Normal, Vec::new()); + logger.warn("skipped file"); + assert_eq!(logged_output(&logger), "Warning: skipped file\n"); + } + + #[test] + fn normal_suppresses_info_messages() { + let logger = Logger::with_writer(Verbosity::Normal, Vec::new()); + logger.info("rendering file"); + assert_eq!(logged_output(&logger), ""); + } + + #[test] + fn verbose_writes_warnings() { + let logger = Logger::with_writer(Verbosity::Verbose, Vec::new()); + logger.warn("skipped file"); + assert_eq!(logged_output(&logger), "Warning: skipped file\n"); + } + + #[test] + fn verbose_writes_info_messages() { + let logger = Logger::with_writer(Verbosity::Verbose, Vec::new()); + logger.info("rendering file"); + assert_eq!(logged_output(&logger), "Info: rendering file\n"); + } + + #[test] + fn verbose_preserves_log_order() { + let logger = Logger::with_writer(Verbosity::Verbose, Vec::new()); + + logger.info("rendering a.rs"); + logger.warn("skipping duplicate file: a.rs"); + logger.info("rendering b.rs"); + + assert_eq!( + logged_output(&logger), + "Info: rendering a.rs\n\ + Warning: skipping duplicate file: a.rs\n\ + Info: rendering b.rs\n" + ); + } + + struct FailingWriter; + + impl Write for FailingWriter { + fn write(&mut self, _buf: &[u8]) -> io::Result<usize> { + Err(io::Error::other("intentional write failure")) } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } + } + + #[test] + fn logging_ignores_writer_errors() { + let logger = Logger::with_writer(Verbosity::Verbose, FailingWriter); + + logger.warn("this should not panic"); + logger.info("this should not panic"); } } |
