#![cfg_attr(coverage_nightly, coverage(off))]
use std::fmt::Write as FmtWrite;
use std::io::{self, Write};
pub trait OutputWriter {
fn status(&mut self, msg: &str);
fn result(&mut self, msg: &str);
fn warning(&mut self, msg: &str);
fn error(&mut self, msg: &str);
fn success(&mut self, msg: &str);
fn info(&mut self, msg: &str);
fn flush(&mut self);
}
#[derive(Default)]
pub struct StdoutWriter {
_private: (),
}
impl StdoutWriter {
pub fn new() -> Self {
Self { _private: () }
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
impl OutputWriter for StdoutWriter {
fn status(&mut self, msg: &str) {
eprintln!("{msg}");
}
fn result(&mut self, msg: &str) {
println!("{msg}");
}
fn warning(&mut self, msg: &str) {
eprintln!("⚠️ {msg}");
}
fn error(&mut self, msg: &str) {
eprintln!("❌ {msg}");
}
fn success(&mut self, msg: &str) {
eprintln!("✅ {msg}");
}
fn info(&mut self, msg: &str) {
eprintln!("🔍 {msg}");
}
fn flush(&mut self) {
let _ = io::stdout().flush();
let _ = io::stderr().flush();
}
}
#[derive(Default, Clone)]
pub struct TestWriter {
pub status_messages: Vec<String>,
pub results: Vec<String>,
pub warnings: Vec<String>,
pub errors: Vec<String>,
pub successes: Vec<String>,
pub infos: Vec<String>,
}
impl TestWriter {
pub fn new() -> Self {
Self::default()
}
pub fn contains(&self, needle: &str) -> bool {
self.all_output().any(|s| s.contains(needle))
}
pub fn result_contains(&self, needle: &str) -> bool {
self.results.iter().any(|s| s.contains(needle))
}
pub fn status_contains(&self, needle: &str) -> bool {
self.status_messages.iter().any(|s| s.contains(needle))
}
pub fn all_output(&self) -> impl Iterator<Item = &String> {
self.status_messages
.iter()
.chain(self.results.iter())
.chain(self.warnings.iter())
.chain(self.errors.iter())
.chain(self.successes.iter())
.chain(self.infos.iter())
}
pub fn combined_output(&self) -> String {
let mut output = String::new();
for msg in self.all_output() {
let _ = writeln!(output, "{msg}");
}
output
}
pub fn clear(&mut self) {
self.status_messages.clear();
self.results.clear();
self.warnings.clear();
self.errors.clear();
self.successes.clear();
self.infos.clear();
}
pub fn is_empty(&self) -> bool {
self.status_messages.is_empty()
&& self.results.is_empty()
&& self.warnings.is_empty()
&& self.errors.is_empty()
&& self.successes.is_empty()
&& self.infos.is_empty()
}
}
impl OutputWriter for TestWriter {
fn status(&mut self, msg: &str) {
self.status_messages.push(msg.to_string());
}
fn result(&mut self, msg: &str) {
self.results.push(msg.to_string());
}
fn warning(&mut self, msg: &str) {
self.warnings.push(msg.to_string());
}
fn error(&mut self, msg: &str) {
self.errors.push(msg.to_string());
}
fn success(&mut self, msg: &str) {
self.successes.push(msg.to_string());
}
fn info(&mut self, msg: &str) {
self.infos.push(msg.to_string());
}
fn flush(&mut self) {
}
}
#[derive(Default)]
pub struct NullWriter;
impl NullWriter {
pub fn new() -> Self {
Self
}
}
impl OutputWriter for NullWriter {
fn status(&mut self, _msg: &str) {}
fn result(&mut self, _msg: &str) {}
fn warning(&mut self, _msg: &str) {}
fn error(&mut self, _msg: &str) {}
fn success(&mut self, _msg: &str) {}
fn info(&mut self, _msg: &str) {}
fn flush(&mut self) {}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stdout_writer_creation() {
let writer = StdoutWriter::new();
drop(writer);
}
#[test]
fn test_test_writer_captures_output() {
let mut writer = TestWriter::new();
writer.status("Processing...");
writer.result("42");
writer.warning("Low memory");
writer.error("Failed");
writer.success("Done");
writer.info("Analyzing");
assert_eq!(writer.status_messages.len(), 1);
assert_eq!(writer.results.len(), 1);
assert_eq!(writer.warnings.len(), 1);
assert_eq!(writer.errors.len(), 1);
assert_eq!(writer.successes.len(), 1);
assert_eq!(writer.infos.len(), 1);
assert!(writer.contains("Processing"));
assert!(writer.contains("42"));
assert!(writer.result_contains("42"));
assert!(writer.status_contains("Processing"));
}
#[test]
fn test_test_writer_combined_output() {
let mut writer = TestWriter::new();
writer.status("A");
writer.result("B");
let combined = writer.combined_output();
assert!(combined.contains("A"));
assert!(combined.contains("B"));
}
#[test]
fn test_test_writer_clear() {
let mut writer = TestWriter::new();
writer.status("msg");
assert!(!writer.is_empty());
writer.clear();
assert!(writer.is_empty());
}
#[test]
fn test_null_writer_discards() {
let mut writer = NullWriter::new();
writer.status("ignored");
writer.result("ignored");
writer.warning("ignored");
writer.error("ignored");
writer.success("ignored");
writer.info("ignored");
writer.flush();
}
}