mod tools; use tools::*; #[test] fn inherit() { let source = program!({ use spork::*; fn print() { eprintln!("test output"); } pub fn main() { let mut table = DispatchTable::new(); table.insert(print); spork(table); let (_, output) = Builder::new() .stderr(OutputMode::Inherit) .spawn(print, ()) .unwrap() .join_with_output() .unwrap(); assert!(output.status.success()); assert_eq!(output.stdout, []); assert_eq!(output.stderr, []); } }); let (stdout, stderr) = run_program(source, ""); assert_eq!(stdout, ""); assert_eq!(stderr, "test output\n"); } #[test] fn disconnected() { let source = program!({ use spork::*; fn print() { eprintln!("test output"); } pub fn main() { let mut table = DispatchTable::new(); table.insert(print); spork(table); let (_, output) = Builder::new() .stderr(OutputMode::Disconnected) .spawn(print, ()) .unwrap() .join_with_output() .unwrap(); assert!(output.status.success()); assert_eq!(output.stdout, []); assert_eq!(output.stderr, []); } }); let (stdout, stderr) = run_program(source, ""); assert_eq!(stdout, ""); assert_eq!(stderr, ""); } #[test] fn saved() { let source = program!({ use spork::*; fn print() { eprintln!("test output"); } pub fn main() { let mut table = DispatchTable::new(); table.insert(print); spork(table); let (_, output) = Builder::new() .stderr(OutputMode::Saved) .spawn(print, ()) .unwrap() .join_with_output() .unwrap(); assert!(output.status.success()); assert_eq!(output.stdout, []); assert_eq!(output.stderr, b"test output\n"); } }); let (stdout, stderr) = run_program(source, ""); assert_eq!(stdout, ""); assert_eq!(stderr, ""); } #[test] fn custom() { let source = program!({ use spork::*; fn print() { eprintln!("test output"); } pub fn main() { let mut table = DispatchTable::new(); table.insert(print); spork(table); let mut temp_path = std::env::current_exe() .unwrap() .parent() .unwrap() .to_path_buf(); temp_path.push("output.txt"); let file = std::fs::File::create(&temp_path).unwrap(); let (_, output) = Builder::new() .stderr(OutputMode::Custom(file.into())) .spawn(print, ()) .unwrap() .join_with_output() .unwrap(); assert!(output.status.success()); assert_eq!(output.stdout, []); assert_eq!(output.stderr, []); let output = std::fs::read_to_string(temp_path).unwrap(); assert_eq!(output, "test output\n"); } }); let (stdout, stderr) = run_program(source, ""); assert_eq!(stdout, ""); assert_eq!(stderr, ""); } #[test] fn buffered_basic() { let source = program!({ use spork::*; fn print() { eprintln!("test output"); } pub fn main() { let mut table = DispatchTable::new(); table.insert(print); spork(table); let (_, output) = Builder::new() .stderr(OutputMode::Buffered { mode: None, device: Box::new(std::io::stderr()), }) .spawn(print, ()) .unwrap() .join_with_output() .unwrap(); assert!(output.status.success()); assert_eq!(output.stdout, []); assert_eq!(output.stderr, []); } }); let (stdout, stderr) = run_program(source, ""); assert_eq!(stdout, ""); assert_eq!(stderr, "test output\n"); } #[test] fn buffered() { let source = program!({ use spork::*; use std::io::Write; use std::time::Duration; fn print() { eprint!("this is "); std::thread::sleep(Duration::from_secs_f32(0.001)); eprintln!("some test"); std::thread::sleep(Duration::from_secs_f32(0.001)); eprintln!("output"); std::thread::sleep(Duration::from_secs_f32(0.001)); eprint!(" for tests"); std::thread::sleep(Duration::from_secs_f32(0.001)); eprint!("."); std::thread::sleep(Duration::from_secs_f32(0.001)); std::io::stderr().flush().unwrap(); std::thread::sleep(Duration::from_secs_f32(0.001)); eprintln!(" other text."); std::thread::sleep(Duration::from_secs_f32(0.001)); } #[derive(PartialEq)] enum HistoryItem { Flush, Write(Vec), } impl std::fmt::Debug for HistoryItem { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { HistoryItem::Flush => f.debug_tuple("Flush").finish(), HistoryItem::Write(buffer) => f .debug_tuple("Write") .field(&String::from_utf8_lossy(buffer)) .finish(), } } } #[derive(Clone)] struct TrackingBuffer { history: std::sync::Arc>>, } impl std::io::Write for TrackingBuffer { fn write(&mut self, buf: &[u8]) -> std::io::Result { self.history .lock() .unwrap() .push(HistoryItem::Write(buf.to_vec())); Ok(buf.len()) } fn flush(&mut self) -> std::io::Result<()> { self.history.lock().unwrap().push(HistoryItem::Flush); Ok(()) } } pub fn main() { let mut table = DispatchTable::new(); table.insert(print); spork(table); // === No buffering === let buffer = TrackingBuffer { history: std::sync::Arc::new(std::sync::Mutex::new(Vec::new())), }; let (_, output) = Builder::new() .stderr(OutputMode::Buffered { mode: None, device: Box::new(buffer.clone()), }) .spawn(print, ()) .unwrap() .join_with_output() .unwrap(); assert!(output.status.success()); assert_eq!(output.stdout, []); assert_eq!(output.stderr, []); assert_eq!( &*buffer.history.lock().unwrap(), &[ HistoryItem::Write(b"this is ".to_vec()), HistoryItem::Write(b"some test\n".to_vec()), HistoryItem::Write(b"output\n".to_vec()), HistoryItem::Write(b" for tests".to_vec()), HistoryItem::Write(b".".to_vec()), HistoryItem::Write(b" other text.\n".to_vec()), HistoryItem::Flush ] ); // === Line buffering === let buffer = TrackingBuffer { history: std::sync::Arc::new(std::sync::Mutex::new(Vec::new())), }; let (_, output) = Builder::new() .stderr(OutputMode::Buffered { mode: Some(BufferMode::Line), device: Box::new(buffer.clone()), }) .spawn(print, ()) .unwrap() .join_with_output() .unwrap(); assert!(output.status.success()); assert_eq!(output.stdout, []); assert_eq!(output.stderr, []); assert_eq!( &*buffer.history.lock().unwrap(), &[ HistoryItem::Write(b"this is some test\n".to_vec()), HistoryItem::Flush, HistoryItem::Write(b"output\n".to_vec()), HistoryItem::Flush, HistoryItem::Write(b" for tests. other text.\n".to_vec()), HistoryItem::Flush ] ); // === Full buffering === let buffer = TrackingBuffer { history: std::sync::Arc::new(std::sync::Mutex::new(Vec::new())), }; let (_, output) = Builder::new() .stderr(OutputMode::Buffered { mode: Some(BufferMode::Full), device: Box::new(buffer.clone()), }) .spawn(print, ()) .unwrap() .join_with_output() .unwrap(); assert!(output.status.success()); assert_eq!(output.stdout, []); assert_eq!(output.stderr, []); assert_eq!( &*buffer.history.lock().unwrap(), &[ HistoryItem::Write( b"this is some test\noutput\n for tests. other text.\n".to_vec() ), HistoryItem::Flush ] ); // === Full buffering === let buffer = TrackingBuffer { history: std::sync::Arc::new(std::sync::Mutex::new(Vec::new())), }; let (_, output) = Builder::new() .stderr(OutputMode::Buffered { mode: Some(BufferMode::Custom(5)), device: Box::new(buffer.clone()), }) .spawn(print, ()) .unwrap() .join_with_output() .unwrap(); assert!(output.status.success()); assert_eq!(output.stdout, []); assert_eq!(output.stderr, []); assert_eq!( &*buffer.history.lock().unwrap(), &[ HistoryItem::Write(b"this ".to_vec()), HistoryItem::Flush, HistoryItem::Write(b"is ".to_vec()), HistoryItem::Flush, HistoryItem::Write(b"some ".to_vec()), HistoryItem::Flush, HistoryItem::Write(b"test\n".to_vec()), HistoryItem::Flush, HistoryItem::Write(b"outpu".to_vec()), HistoryItem::Flush, HistoryItem::Write(b"t\n".to_vec()), HistoryItem::Flush, HistoryItem::Write(b" for ".to_vec()), HistoryItem::Flush, HistoryItem::Write(b"tests".to_vec()), HistoryItem::Flush, HistoryItem::Write(b".".to_vec()), HistoryItem::Flush, HistoryItem::Write(b" othe".to_vec()), HistoryItem::Flush, HistoryItem::Write(b"r tex".to_vec()), HistoryItem::Flush, HistoryItem::Write(b"t.\n".to_vec()), HistoryItem::Flush ] ); } }); let (stdout, stderr) = run_program(source, ""); assert_eq!(stdout, ""); assert_eq!(stderr, ""); }