use std::fmt::Arguments;
use std::io::{self, Write};
#[cfg_attr(test, mutants::skip)]
pub(crate) fn print_line(message: Arguments<'_>) -> io::Result<()> {
write_result(&mut io::stdout().lock(), format_args!("{message}\n"))
}
#[cfg_attr(test, mutants::skip)]
pub(crate) fn note_line(message: Arguments<'_>) {
write_diagnostic(&mut io::stderr().lock(), message);
}
#[cfg_attr(test, mutants::skip)]
pub(crate) fn print_prompt(message: Arguments<'_>) -> io::Result<()> {
write_result(&mut io::stderr().lock(), message)
}
fn write_result(writer: &mut impl Write, message: Arguments<'_>) -> io::Result<()> {
write!(writer, "{message}")?;
writer.flush()
}
fn write_diagnostic(writer: &mut impl Write, message: Arguments<'_>) {
_ = writeln!(writer, "{message}");
_ = writer.flush();
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use std::io::ErrorKind;
use super::*;
#[derive(Default)]
struct RecordingWriter {
bytes: Vec<u8>,
flushed: Option<Vec<u8>>,
write_error: Option<ErrorKind>,
flush_error: Option<ErrorKind>,
}
impl Write for RecordingWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
if let Some(kind) = self.write_error {
return Err(kind.into());
}
self.bytes.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
self.flushed = Some(self.bytes.clone());
self.flush_error.map_or(Ok(()), |kind| Err(kind.into()))
}
}
#[test]
fn result_formats_and_flushes_without_adding_a_newline() {
let mut writer = RecordingWriter::default();
let value = "payload";
write_result(&mut writer, format_args!("result {value}")).unwrap();
assert_eq!(writer.bytes, b"result payload");
assert_eq!(
writer.flushed.as_deref(),
Some(b"result payload".as_slice())
);
}
#[test]
fn result_propagates_write_failure_without_flushing() {
let mut writer = RecordingWriter {
write_error: Some(ErrorKind::BrokenPipe),
flush_error: Some(ErrorKind::PermissionDenied),
..RecordingWriter::default()
};
let error = write_result(&mut writer, format_args!("result")).unwrap_err();
assert_eq!(error.kind(), ErrorKind::BrokenPipe);
assert!(writer.bytes.is_empty());
assert!(writer.flushed.is_none());
}
#[test]
fn result_propagates_flush_failure() {
let mut writer = RecordingWriter {
flush_error: Some(ErrorKind::BrokenPipe),
..RecordingWriter::default()
};
let error = write_result(&mut writer, format_args!("result")).unwrap_err();
assert_eq!(error.kind(), ErrorKind::BrokenPipe);
assert_eq!(writer.flushed.as_deref(), Some(b"result".as_slice()));
}
#[test]
fn diagnostic_formats_a_line_and_flushes() {
let mut writer = RecordingWriter::default();
let value = "payload";
write_diagnostic(&mut writer, format_args!("note {value}"));
assert_eq!(writer.bytes, b"note payload\n");
assert_eq!(
writer.flushed.as_deref(),
Some(b"note payload\n".as_slice())
);
}
#[test]
fn diagnostic_still_flushes_after_write_failure() {
let mut writer = RecordingWriter {
bytes: b"earlier note".to_vec(),
write_error: Some(ErrorKind::BrokenPipe),
..RecordingWriter::default()
};
write_diagnostic(&mut writer, format_args!("next note"));
assert_eq!(writer.flushed.as_deref(), Some(b"earlier note".as_slice()));
}
#[test]
fn diagnostic_tolerates_flush_failure() {
let mut writer = RecordingWriter {
flush_error: Some(ErrorKind::BrokenPipe),
..RecordingWriter::default()
};
write_diagnostic(&mut writer, format_args!("note"));
assert_eq!(writer.flushed.as_deref(), Some(b"note\n".as_slice()));
}
#[test]
fn diagnostic_tolerates_both_failures() {
let mut writer = RecordingWriter {
write_error: Some(ErrorKind::BrokenPipe),
flush_error: Some(ErrorKind::PermissionDenied),
..RecordingWriter::default()
};
write_diagnostic(&mut writer, format_args!("note"));
assert_eq!(writer.flushed.as_deref(), Some(b"".as_slice()));
}
}