use expectrl::{Captures, ControlCode, Needle, Session}; use std::{process::Command, thread, time::Duration}; #[cfg(unix)] use expectrl::WaitStatus; #[cfg(feature = "async")] use futures_lite::{ future::block_on, io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt}, }; #[cfg(not(feature = "async"))] use std::io::{BufRead, Read, Write}; #[test] #[cfg(unix)] fn send_controll() { let mut proc = Session::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 = Session::spawn(Command::new("powershell -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 = Session::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 = Session::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 = Session::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 = Session::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 = Session::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)] #[cfg(not(feature = "async"))] fn try_read_by_byte() { // it shows that on windows ECHO is turned on. // Mustn't it be turned down? let mut proc = Session::spawn(Command::new("powershell")).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 = Session::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 = Session::spawn(Command::new("powershell")).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 = Session::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 = Session::spawn(Command::new("powershell")).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 = Session::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 = Session::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 = Session::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 = Session::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 = Session::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 = Session::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 = Session::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(not(target_os = "macos"))] #[cfg(not(windows))] fn automatic_stop_of_interact_on_eof() { let mut p = Session::spawn(Command::new("ls")).unwrap(); _p_interact(&mut p).unwrap(); // check that second spawn works let mut p = Session::spawn(Command::new("ls")).unwrap(); _p_interact(&mut p).unwrap(); } #[test] #[cfg(not(target_os = "macos"))] #[cfg(not(windows))] fn spawn_after_interact() { let mut p = Session::spawn(Command::new("ls")).unwrap(); _p_interact(&mut p).unwrap(); let p = Session::spawn(Command::new("ls")).unwrap(); assert!(matches!( p.get_process().wait().unwrap(), WaitStatus::Exited(_, 0) )); } #[test] #[cfg(unix)] fn read_line_test() { let mut proc = Session::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 Session, buf: &mut [u8]) -> std::io::Result { #[cfg(not(feature = "async"))] { proc.read(buf) } #[cfg(feature = "async")] { block_on(proc.read(buf)) } } fn _p_write_all(proc: &mut Session, buf: &[u8]) -> std::io::Result<()> { #[cfg(not(feature = "async"))] { proc.write_all(buf) } #[cfg(feature = "async")] { block_on(proc.write_all(buf)) } } fn _p_flush(proc: &mut Session) -> std::io::Result<()> { #[cfg(not(feature = "async"))] { proc.flush() } #[cfg(feature = "async")] { block_on(proc.flush()) } } fn _p_send(proc: &mut Session, buf: &str) -> std::io::Result<()> { #[cfg(not(feature = "async"))] { proc.send(buf) } #[cfg(feature = "async")] { block_on(proc.send(buf)) } } fn _p_expect(proc: &mut Session, n: impl Needle) -> Result { #[cfg(not(feature = "async"))] { proc.expect(n) } #[cfg(feature = "async")] { block_on(proc.expect(n)) } } fn _p_send_line(proc: &mut Session, buf: &str) -> std::io::Result<()> { #[cfg(not(feature = "async"))] { proc.send_line(buf) } #[cfg(feature = "async")] { block_on(proc.send_line(buf)) } } fn _p_send_control(proc: &mut Session, buf: impl Into) -> std::io::Result<()> { #[cfg(not(feature = "async"))] { proc.send(buf.into()) } #[cfg(feature = "async")] { block_on(proc.send(buf.into())) } } fn _p_read_to_string(proc: &mut Session) -> std::io::Result { let mut buf = String::new(); #[cfg(not(feature = "async"))] { proc.read_to_string(&mut buf)?; } #[cfg(feature = "async")] { block_on(proc.read_to_string(&mut buf))?; } Ok(buf) } fn _p_read_to_end(proc: &mut Session) -> std::io::Result> { let mut buf = Vec::new(); #[cfg(not(feature = "async"))] { proc.read_to_end(&mut buf)?; } #[cfg(feature = "async")] { block_on(proc.read_to_end(&mut buf))?; } Ok(buf) } fn _p_read_until(proc: &mut Session, ch: u8) -> std::io::Result> { let mut buf = Vec::new(); #[cfg(not(feature = "async"))] { let n = proc.read_until(ch, &mut buf)?; buf = buf[..n].to_vec(); } #[cfg(feature = "async")] { let n = block_on(proc.read_until(ch, &mut buf))?; buf = buf[..n].to_vec(); } Ok(buf) } fn _p_read_line(proc: &mut Session) -> std::io::Result { let mut buf = String::new(); #[cfg(not(feature = "async"))] { proc.read_line(&mut buf)?; } #[cfg(feature = "async")] { block_on(proc.read_line(&mut buf))?; } Ok(buf) } fn _p_is_empty(proc: &mut Session) -> std::io::Result { #[cfg(not(feature = "async"))] { proc.is_empty() } #[cfg(feature = "async")] { block_on(proc.is_empty()) } } fn _p_try_read(proc: &mut Session, buf: &mut [u8]) -> std::io::Result { #[cfg(not(feature = "async"))] { proc.try_read(buf) } #[cfg(feature = "async")] { block_on(async { futures_lite::future::poll_once(proc.read(buf)) .await .unwrap_or(Err(std::io::Error::new(std::io::ErrorKind::WouldBlock, ""))) }) } } #[cfg(unix)] fn _p_interact(proc: &mut Session) -> Result<(), expectrl::Error> { use expectrl::{interact::InteractOptions, stream::stdin::Stdin}; use std::io::stdout; let mut stdin = Stdin::open()?; let stdout = stdout(); #[cfg(not(feature = "async"))] { proc.interact(&mut stdin, stdout) .spawn(InteractOptions::default())?; } #[cfg(feature = "async")] { block_on( proc.interact(&mut stdin, stdout) .spawn(InteractOptions::default()), )?; } stdin.close() } #[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; }