| |
| |
| |
|
|
| use std::io::{self, Stderr, Stdout, Write}; |
| use std::sync::{Arc, Mutex}; |
|
|
| use forge_domain::ConsoleWriter; |
|
|
| |
| |
| |
| |
| |
| |
| |
| #[derive(Debug)] |
| pub struct StdConsoleWriter<O = Stdout, E = Stderr> { |
| stdout: Arc<Mutex<O>>, |
| stderr: Arc<Mutex<E>>, |
| } |
|
|
| impl<O, E> Clone for StdConsoleWriter<O, E> { |
| fn clone(&self) -> Self { |
| Self { stdout: self.stdout.clone(), stderr: self.stderr.clone() } |
| } |
| } |
|
|
| impl Default for StdConsoleWriter<Stdout, Stderr> { |
| fn default() -> Self { |
| Self { |
| stdout: Arc::new(Mutex::new(io::stdout())), |
| stderr: Arc::new(Mutex::new(io::stderr())), |
| } |
| } |
| } |
|
|
| impl<O, E> StdConsoleWriter<O, E> { |
| |
| pub fn with_writers(stdout: O, stderr: E) -> Self { |
| Self { |
| stdout: Arc::new(Mutex::new(stdout)), |
| stderr: Arc::new(Mutex::new(stderr)), |
| } |
| } |
| } |
|
|
| impl<O: Write + Send, E: Write + Send> ConsoleWriter for StdConsoleWriter<O, E> { |
| fn write(&self, buf: &[u8]) -> io::Result<usize> { |
| let mut guard = self.stdout.lock().unwrap_or_else(|e| e.into_inner()); |
| guard.write(buf) |
| } |
|
|
| fn write_err(&self, buf: &[u8]) -> io::Result<usize> { |
| let mut guard = self.stderr.lock().unwrap_or_else(|e| e.into_inner()); |
| guard.write(buf) |
| } |
|
|
| fn flush(&self) -> io::Result<()> { |
| let mut guard = self.stdout.lock().unwrap_or_else(|e| e.into_inner()); |
| guard.flush() |
| } |
|
|
| fn flush_err(&self) -> io::Result<()> { |
| let mut guard = self.stderr.lock().unwrap_or_else(|e| e.into_inner()); |
| guard.flush() |
| } |
| } |
|
|
| #[cfg(test)] |
| mod tests { |
| use std::io::Cursor; |
| use std::thread; |
|
|
| use bstr::ByteSlice; |
|
|
| use super::*; |
|
|
| #[test] |
| fn test_concurrent_writes_dont_interleave() { |
| let stdout = Cursor::new(Vec::new()); |
| let stderr = Cursor::new(Vec::new()); |
| let printer = StdConsoleWriter::with_writers(stdout, stderr); |
| let p1 = printer.clone(); |
| let p2 = printer.clone(); |
|
|
| let h1 = thread::spawn(move || { |
| p1.write(b"AAAA").unwrap(); |
| p1.write(b"BBBB").unwrap(); |
| p1.flush().unwrap(); |
| }); |
|
|
| let h2 = thread::spawn(move || { |
| p2.write(b"XXXX").unwrap(); |
| p2.write(b"ZZZZ").unwrap(); |
| p2.flush().unwrap(); |
| }); |
|
|
| h1.join().unwrap(); |
| h2.join().unwrap(); |
|
|
| |
| |
| |
| let actual = printer.stdout.lock().unwrap().get_ref().clone(); |
| let valid_orderings = [ |
| b"AAAABBBBXXXXZZZZ".to_vec(), |
| b"XXXXZZZZAAAABBBB".to_vec(), |
| b"AAAAXXXXBBBBZZZZ".to_vec(), |
| b"AAAAXXXXZZZZBBBB".to_vec(), |
| b"XXXXAAAABBBBZZZZ".to_vec(), |
| b"XXXXAAAAZZZZBBBB".to_vec(), |
| ]; |
| assert!( |
| valid_orderings.contains(&actual), |
| "Output was interleaved: {:?}", |
| actual.as_slice().to_str_lossy() |
| ); |
| } |
|
|
| #[test] |
| fn test_with_mock_writer() { |
| let stdout = Cursor::new(Vec::new()); |
| let stderr = Cursor::new(Vec::new()); |
| let printer = StdConsoleWriter::with_writers(stdout, stderr); |
|
|
| printer.write(b"hello").unwrap(); |
| printer.write_err(b"error").unwrap(); |
|
|
| let stdout_content = printer.stdout.lock().unwrap().get_ref().clone(); |
| let stderr_content = printer.stderr.lock().unwrap().get_ref().clone(); |
|
|
| assert_eq!(stdout_content, b"hello"); |
| assert_eq!(stderr_content, b"error"); |
| } |
| } |
|
|