use anticipate::{Captures, ControlCode, DefaultSession, Needle}; use std::{process::Command, thread, time::Duration}; #[cfg(unix)] use anticipate::WaitStatus; use std::io::{BufRead, Read, Write}; #[test] #[cfg(unix)] fn send_controll() { let mut proc = DefaultSession::spawn(Command::new("cat")).unwrap(); _p_send_control(&mut proc, ControlCode::EOT).unwrap(); assert_eq!( proc.get_process().wait().unwrap(), WaitStatus::Exited(proc.get_process().pid(), 0), ); } #[test] #[cfg(windows)] fn send_controll() { let mut proc = DefaultSession::spawn(Command::new("pwsh -C ping localhost")) .unwrap(); // give powershell a bit time thread::sleep(Duration::from_millis(100)); _p_send_control(&mut proc, ControlCode::ETX).unwrap(); assert!({ let code = proc.get_process().wait(None).unwrap(); code == 0 || code == 3221225786 }); } #[test] #[cfg(unix)] fn send() { let mut proc = DefaultSession::spawn(Command::new("cat")).unwrap(); _p_send(&mut proc, "hello cat\n").unwrap(); // give cat a time to react on input thread::sleep(Duration::from_millis(100)); let mut buf = vec![0; 128]; let n = _p_read(&mut proc, &mut buf).unwrap(); assert_eq!(&buf[..n], b"hello cat\r\n"); assert!(proc.get_process_mut().exit(true).unwrap()); } #[test] #[cfg(windows)] fn send() { let mut proc = DefaultSession::spawn(Command::new( "python ./tests/actions/cat/main.py", )) .unwrap(); _p_send(&mut proc, "hello cat\r\n").unwrap(); _p_expect(&mut proc, "hello cat").unwrap(); proc.get_process_mut().exit(0).unwrap(); } #[test] #[cfg(unix)] fn send_line() { let mut proc = DefaultSession::spawn(Command::new("cat")).unwrap(); _p_send_line(&mut proc, "hello cat").unwrap(); // give cat a time to react on input thread::sleep(Duration::from_millis(100)); let mut buf = vec![0; 128]; let n = _p_read(&mut proc, &mut buf).unwrap(); assert_eq!(&buf[..n], b"hello cat\r\n"); assert!(proc.get_process_mut().exit(true).unwrap()); } #[test] #[cfg(windows)] fn send_line() { let mut proc = DefaultSession::spawn(Command::new( "python ./tests/actions/cat/main.py", )) .unwrap(); _p_send_line(&mut proc, "hello cat").unwrap(); _p_expect(&mut proc, "hello cat").unwrap(); proc.get_process_mut().exit(0).unwrap(); } #[test] #[cfg(unix)] fn try_read_by_byte() { let mut proc = DefaultSession::spawn(Command::new("cat")).unwrap(); assert_eq!( _p_try_read(&mut proc, &mut [0; 1]).unwrap_err().kind(), std::io::ErrorKind::WouldBlock ); _p_send_line(&mut proc, "123").unwrap(); // give cat a time to react on input thread::sleep(Duration::from_millis(100)); let mut buf = [0; 1]; _p_try_read(&mut proc, &mut buf).unwrap(); assert_eq!(&buf, &[b'1']); _p_try_read(&mut proc, &mut buf).unwrap(); assert_eq!(&buf, &[b'2']); _p_try_read(&mut proc, &mut buf).unwrap(); assert_eq!(&buf, &[b'3']); _p_try_read(&mut proc, &mut buf).unwrap(); assert_eq!(&buf, &[b'\r']); _p_try_read(&mut proc, &mut buf).unwrap(); assert_eq!(&buf, &[b'\n']); assert_eq!( _p_try_read(&mut proc, &mut buf).unwrap_err().kind(), std::io::ErrorKind::WouldBlock ); } #[test] #[cfg(windows)] fn try_read_by_byte() { // it shows that on windows ECHO is turned on. // Mustn't it be turned down? let mut proc = DefaultSession::spawn(Command::new("pwsh")).unwrap(); _p_send_line( &mut proc, "while (1) { read-host | set r; if (!$r) { break }}", ) .unwrap(); _p_read_until(&mut proc, b'}').unwrap(); _p_read_line(&mut proc).unwrap(); _p_send_line(&mut proc, "123").unwrap(); thread::sleep(Duration::from_millis(500)); _p_read_until(&mut proc, b'1').unwrap(); let mut buf = [0; 1]; _p_try_read(&mut proc, &mut buf).unwrap(); assert_eq!(&buf, &[b'2']); _p_try_read(&mut proc, &mut buf).unwrap(); assert_eq!(&buf, &[b'3']); _p_try_read(&mut proc, &mut buf).unwrap(); assert_eq!(&buf, &[b'\r']); _p_try_read(&mut proc, &mut buf).unwrap(); assert_eq!(&buf, &[b'\n']); } #[test] #[cfg(unix)] fn blocking_read_after_non_blocking() { let mut proc = DefaultSession::spawn(Command::new("cat")).unwrap(); assert!(_p_is_empty(&mut proc).unwrap()); _p_send_line(&mut proc, "123").unwrap(); // give cat a time to react on input thread::sleep(Duration::from_millis(100)); let mut buf = [0; 1]; _p_try_read(&mut proc, &mut buf).unwrap(); assert_eq!(&buf, &[b'1']); let mut buf = [0; 64]; let n = _p_read(&mut proc, &mut buf).unwrap(); assert_eq!(&buf[..n], b"23\r\n"); thread::spawn(move || { let _ = _p_read(&mut proc, &mut buf).unwrap(); // the error will be propagated in case of panic panic!("it's unnexpected that read operation will be ended") }); // give some time to read thread::sleep(Duration::from_millis(100)); } #[test] #[cfg(windows)] fn blocking_read_after_non_blocking() { let mut proc = DefaultSession::spawn(Command::new("pwsh")).unwrap(); _p_send_line( &mut proc, "while (1) { read-host | set r; if (!$r) { break }}", ) .unwrap(); thread::sleep(Duration::from_millis(300)); _p_send_line(&mut proc, "123").unwrap(); thread::sleep(Duration::from_millis(1000)); assert!(do_until( || { thread::sleep(Duration::from_millis(50)); _p_try_read(&mut proc, &mut [0; 1]).is_ok() }, Duration::from_secs(3) )); let mut buf = [0; 64]; let n = _p_read(&mut proc, &mut buf).unwrap(); assert!(n > 0); } #[test] #[cfg(unix)] fn try_read() { let mut proc = DefaultSession::spawn(Command::new("cat")).unwrap(); let mut buf = vec![0; 128]; assert_eq!( _p_try_read(&mut proc, &mut buf).unwrap_err().kind(), std::io::ErrorKind::WouldBlock ); _p_send_line(&mut proc, "123").unwrap(); // give cat a time to react on input thread::sleep(Duration::from_millis(100)); assert_eq!(_p_try_read(&mut proc, &mut buf).unwrap(), 5); assert_eq!(&buf[..5], b"123\r\n"); assert_eq!( _p_try_read(&mut proc, &mut buf).unwrap_err().kind(), std::io::ErrorKind::WouldBlock ); } #[test] #[cfg(windows)] fn try_read() { let mut proc = DefaultSession::spawn(Command::new("pwsh")).unwrap(); thread::sleep(Duration::from_millis(300)); _p_send_line( &mut proc, "while (1) { read-host | set r; if (!$r) { break }}", ) .unwrap(); thread::sleep(Duration::from_millis(500)); _p_send_line(&mut proc, "123").unwrap(); _p_send_line(&mut proc, "123").unwrap(); // give cat a time to react on input thread::sleep(Duration::from_millis(1500)); assert!(do_until( || { thread::sleep(Duration::from_millis(50)); let mut buf = vec![0; 128]; let _ = _p_try_read(&mut proc, &mut buf); if String::from_utf8_lossy(&buf).contains("123") { true } else { false } }, Duration::from_secs(5) )); } #[test] #[cfg(unix)] fn blocking_read_after_non_blocking_try_read() { let mut proc = DefaultSession::spawn(Command::new("cat")).unwrap(); let mut buf = vec![0; 1]; assert_eq!( _p_try_read(&mut proc, &mut buf).unwrap_err().kind(), std::io::ErrorKind::WouldBlock ); _p_send_line(&mut proc, "123").unwrap(); // give cat a time to react on input thread::sleep(Duration::from_millis(100)); assert_eq!(_p_try_read(&mut proc, &mut buf).unwrap(), 1); assert_eq!(&buf[..1], b"1"); let mut buf = [0; 64]; let n = _p_read(&mut proc, &mut buf).unwrap(); assert_eq!(&buf[..n], b"23\r\n"); thread::spawn(move || { let _ = _p_read(&mut proc, &mut buf).unwrap(); // the error will be propagated in case of panic panic!("it's unnexpected that read operation will be ended") }); // give some time to read thread::sleep(Duration::from_millis(100)); } #[cfg(unix)] #[test] fn try_read_after_eof() { let mut proc = DefaultSession::spawn(Command::new("cat")).unwrap(); _p_send_line(&mut proc, "hello").unwrap(); // give cat a time to react on input thread::sleep(Duration::from_millis(100)); let mut buf = vec![0; 128]; assert_eq!(_p_try_read(&mut proc, &mut buf).unwrap(), 7); assert_eq!( _p_try_read(&mut proc, &mut buf).unwrap_err().kind(), std::io::ErrorKind::WouldBlock ); assert!(_p_is_empty(&mut proc).unwrap()); } #[test] #[cfg(unix)] fn try_read_after_process_exit() { let mut command = Command::new("echo"); command.arg("hello cat"); let mut proc = DefaultSession::spawn(command).unwrap(); assert_eq!( proc.get_process().wait().unwrap(), WaitStatus::Exited(proc.get_process().pid(), 0) ); #[cfg(target_os = "linux")] assert_eq!(_p_try_read(&mut proc, &mut [0; 128]).unwrap(), 11); #[cfg(not(target_os = "linux"))] assert_eq!(_p_try_read(&mut proc, &mut [0; 128]).unwrap(), 0); assert_eq!(_p_try_read(&mut proc, &mut [0; 128]).unwrap(), 0); assert!(_p_is_empty(&mut proc).unwrap()); // // on macos we may not able to read after process is dead. // // I assume that kernel consumes proceses resorces without any code check of parent, // // which what is happening on linux. // // // // So we check that there may be None or Some(0) // // on macos we can't put it before read's for some reason something get blocked // // assert_eq!(proc.wait().unwrap(), WaitStatus::Exited(proc.pid(), 0)); } #[cfg(windows)] #[test] fn try_read_after_process_exit() { use std::io::ErrorKind; let mut proc = DefaultSession::spawn(Command::new("cmd /C echo hello cat")).unwrap(); assert_eq!(proc.get_process().wait(None).unwrap(), 0); let now = std::time::Instant::now(); loop { if now.elapsed() > Duration::from_secs(2) { panic!("didn't read what expected") } match _p_try_read(&mut proc, &mut [0; 128]) { Ok(n) => { assert!(n > 0); assert!(_p_try_read(&mut proc, &mut [0; 128]).is_err()); assert!(_p_try_read(&mut proc, &mut [0; 128]).is_err()); assert!(_p_is_empty(&mut proc).unwrap()); assert_eq!(proc.get_process().wait(None).unwrap(), 0); return; } Err(err) => { if err.kind() == ErrorKind::WouldBlock { continue; } panic!("unexpected error {:?}", err); } } } } #[test] #[cfg(unix)] fn try_read_to_end() { let mut cmd = Command::new("echo"); cmd.arg("Hello World"); let mut proc = DefaultSession::spawn(cmd).unwrap(); let mut buf: Vec = Vec::new(); loop { let mut b = [0; 128]; match _p_try_read(&mut proc, &mut b) { Ok(0) => break, Ok(n) => buf.extend(&b[..n]), Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => {} Err(err) => Err(err).unwrap(), } } assert_eq!(&buf[..13], b"Hello World\r\n"); } #[test] #[cfg(windows)] fn try_read_to_end() { let mut proc = DefaultSession::spawn(Command::new( "python ./tests/actions/echo/main.py Hello World", )) .unwrap(); let mut buf: Vec = Vec::new(); let now = std::time::Instant::now(); while now.elapsed() < Duration::from_secs(1) { let mut b = [0; 1]; match _p_try_read(&mut proc, &mut b) { Ok(n) => buf.extend(&b[..n]), Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => (), Err(err) => Err(err).unwrap(), } } assert!(String::from_utf8_lossy(&buf).contains("Hello World")); } #[test] #[cfg(windows)] fn continues_try_reads() { let cmd = Command::new("python3 -c \"import time; print('Start Sleep'); time.sleep(0.1); print('End of Sleep'); yn=input('input');\""); let mut proc = DefaultSession::spawn(cmd).unwrap(); let mut buf = [0; 128]; loop { if !proc.is_alive().unwrap() { panic!("Most likely python is not installed"); } match _p_try_read(&mut proc, &mut buf) { Ok(n) => { if String::from_utf8_lossy(&buf[..n]).contains("input") { break; } } Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => {} Err(err) => Err(err).unwrap(), } } } #[test] #[cfg(unix)] fn read_line_test() { let mut proc = DefaultSession::spawn(Command::new("cat")).unwrap(); // give cat a time to react on input thread::sleep(Duration::from_millis(100)); _p_send_line(&mut proc, "123").unwrap(); thread::sleep(Duration::from_millis(100)); let line = _p_read_line(&mut proc).unwrap(); assert_eq!(&line, "123\r\n"); proc.get_process_mut().exit(true).unwrap(); } fn _p_read( proc: &mut DefaultSession, buf: &mut [u8], ) -> std::io::Result { proc.read(buf) } fn _p_write_all( proc: &mut DefaultSession, buf: &[u8], ) -> std::io::Result<()> { proc.write_all(buf) } fn _p_flush(proc: &mut DefaultSession) -> std::io::Result<()> { proc.flush() } fn _p_send(proc: &mut DefaultSession, buf: &str) -> std::io::Result<()> { proc.send(buf) } fn _p_expect( proc: &mut DefaultSession, n: impl Needle, ) -> Result { proc.expect(n) } fn _p_send_line(proc: &mut DefaultSession, buf: &str) -> std::io::Result<()> { proc.send_line(buf) } fn _p_send_control( proc: &mut DefaultSession, buf: impl Into, ) -> std::io::Result<()> { proc.send(buf.into()) } fn _p_read_to_string(proc: &mut DefaultSession) -> std::io::Result { let mut buf = String::new(); proc.read_to_string(&mut buf)?; Ok(buf) } fn _p_read_to_end(proc: &mut DefaultSession) -> std::io::Result> { let mut buf = Vec::new(); proc.read_to_end(&mut buf)?; Ok(buf) } fn _p_read_until( proc: &mut DefaultSession, ch: u8, ) -> std::io::Result> { let mut buf = Vec::new(); let n = proc.read_until(ch, &mut buf)?; buf = buf[..n].to_vec(); Ok(buf) } fn _p_read_line(proc: &mut DefaultSession) -> std::io::Result { let mut buf = String::new(); proc.read_line(&mut buf)?; Ok(buf) } fn _p_is_empty(proc: &mut DefaultSession) -> std::io::Result { proc.is_empty() } fn _p_try_read( proc: &mut DefaultSession, buf: &mut [u8], ) -> std::io::Result { proc.try_read(buf) } #[cfg(windows)] fn do_until(mut foo: impl FnMut() -> bool, timeout: Duration) -> bool { let now = std::time::Instant::now(); while now.elapsed() < timeout { if foo() { return true; } } return false; }