mod helpers; #[test] fn test_multiple() { let pty = pty_process::blocking::Pty::new().unwrap(); let pts = pty.pts().unwrap(); pty.resize(pty_process::Size::new(24, 80)).unwrap(); let mut child = pty_process::blocking::Command::new("echo") .arg("foo") .spawn(&pts) .unwrap(); let mut output = helpers::output(&pty); assert_eq!(output.next().unwrap(), "foo\r\n"); let status = child.wait().unwrap(); assert_eq!(status.code().unwrap(), 0); let mut child = pty_process::blocking::Command::new("echo") .arg("bar") .spawn(&pts) .unwrap(); assert_eq!(output.next().unwrap(), "bar\r\n"); let status = child.wait().unwrap(); assert_eq!(status.code().unwrap(), 0); } #[cfg(feature = "async")] #[tokio::test] async fn test_multiple_async() { use futures::stream::StreamExt as _; let mut pty = pty_process::Pty::new().unwrap(); let pts = pty.pts().unwrap(); pty.resize(pty_process::Size::new(24, 80)).unwrap(); let mut child = pty_process::Command::new("echo") .arg("foo") .spawn(&pts) .unwrap(); let (pty_r, _) = pty.split(); let mut output = helpers::output_async(pty_r); assert_eq!(output.next().await.unwrap(), "foo\r\n"); let status = child.wait().await.unwrap(); assert_eq!(status.code().unwrap(), 0); let mut child = pty_process::Command::new("echo") .arg("bar") .spawn(&pts) .unwrap(); assert_eq!(output.next().await.unwrap(), "bar\r\n"); let status = child.wait().await.unwrap(); assert_eq!(status.code().unwrap(), 0); } #[test] fn test_multiple_configured() { use std::io::BufRead as _; use std::os::fd::AsRawFd as _; let pty = pty_process::blocking::Pty::new().unwrap(); let pts = pty.pts().unwrap(); pty.resize(pty_process::Size::new(24, 80)).unwrap(); let (stderr_pipe_r, stderr_pipe_w) = pipe(); let mut stderr_pipe_r = std::io::BufReader::new(std::fs::File::from(stderr_pipe_r)); let (pre_exec_pipe_r, pre_exec_pipe_w) = pipe(); let mut pre_exec_pipe_r = std::io::BufReader::new(std::fs::File::from(pre_exec_pipe_r)); let mut cmd = pty_process::blocking::Command::new("perl"); cmd.arg("-Esay 'foo'; say STDERR 'foo-stderr'; open my $fh, '>&=3'; say $fh 'foo-3';") .stderr(std::process::Stdio::from(stderr_pipe_w)); unsafe { cmd.pre_exec(move || { nix::unistd::dup2(pre_exec_pipe_w.as_raw_fd(), 3)?; nix::fcntl::fcntl( 3, nix::fcntl::F_SETFD(nix::fcntl::FdFlag::empty()), )?; Ok(()) }); } let mut child = cmd.spawn(&pts).unwrap(); let mut output = helpers::output(&pty); assert_eq!(output.next().unwrap(), "foo\r\n"); let mut buf = vec![]; nix::unistd::alarm::set(5); stderr_pipe_r.read_until(b'\n', &mut buf).unwrap(); nix::unistd::alarm::cancel(); assert_eq!(std::string::String::from_utf8(buf).unwrap(), "foo-stderr\n"); let mut buf = vec![]; nix::unistd::alarm::set(5); pre_exec_pipe_r.read_until(b'\n', &mut buf).unwrap(); nix::unistd::alarm::cancel(); assert_eq!(std::string::String::from_utf8(buf).unwrap(), "foo-3\n"); let status = child.wait().unwrap(); assert_eq!(status.code().unwrap(), 0); let mut child = cmd.spawn(&pts).unwrap(); let mut output = helpers::output(&pty); assert_eq!(output.next().unwrap(), "foo\r\n"); let mut buf = vec![]; nix::unistd::alarm::set(5); stderr_pipe_r.read_until(b'\n', &mut buf).unwrap(); nix::unistd::alarm::cancel(); assert_eq!(std::string::String::from_utf8(buf).unwrap(), "foo-stderr\n"); let mut buf = vec![]; nix::unistd::alarm::set(5); pre_exec_pipe_r.read_until(b'\n', &mut buf).unwrap(); nix::unistd::alarm::cancel(); assert_eq!(std::string::String::from_utf8(buf).unwrap(), "foo-3\n"); let status = child.wait().unwrap(); assert_eq!(status.code().unwrap(), 0); } #[cfg(feature = "async")] #[tokio::test] async fn test_multiple_configured_async() { use futures::stream::StreamExt as _; use std::os::fd::{AsRawFd as _, FromRawFd as _, IntoRawFd as _}; use tokio::io::AsyncBufReadExt as _; let mut pty = pty_process::Pty::new().unwrap(); let pts = pty.pts().unwrap(); pty.resize(pty_process::Size::new(24, 80)).unwrap(); let (pty_r, _) = pty.split(); let (stderr_pipe_r, stderr_pipe_w) = pipe(); let mut stderr_pipe_r = tokio::io::BufReader::new(unsafe { tokio::fs::File::from_raw_fd(stderr_pipe_r.into_raw_fd()) }); let (pre_exec_pipe_r, pre_exec_pipe_w) = pipe(); let mut pre_exec_pipe_r = tokio::io::BufReader::new(unsafe { tokio::fs::File::from_raw_fd(pre_exec_pipe_r.into_raw_fd()) }); let mut cmd = pty_process::Command::new("perl"); cmd.arg( "-Esay 'foo'; \ say STDERR 'foo-stderr'; \ open my $fh, '>&=3'; \ say $fh 'foo-3';", ) .stderr(std::process::Stdio::from(stderr_pipe_w)); unsafe { cmd.pre_exec(move || { nix::unistd::dup2(pre_exec_pipe_w.as_raw_fd(), 3)?; nix::fcntl::fcntl( 3, nix::fcntl::F_SETFD(nix::fcntl::FdFlag::empty()), )?; Ok(()) }); } let mut child = cmd.spawn(&pts).unwrap(); let mut output = helpers::output_async(pty_r); assert_eq!(output.next().await.unwrap(), "foo\r\n"); let mut buf = vec![]; tokio::time::timeout( std::time::Duration::from_secs(5), stderr_pipe_r.read_until(b'\n', &mut buf), ) .await .unwrap() .unwrap(); assert_eq!(std::string::String::from_utf8(buf).unwrap(), "foo-stderr\n"); let mut buf = vec![]; tokio::time::timeout( std::time::Duration::from_secs(5), pre_exec_pipe_r.read_until(b'\n', &mut buf), ) .await .unwrap() .unwrap(); assert_eq!(std::string::String::from_utf8(buf).unwrap(), "foo-3\n"); let status = child.wait().await.unwrap(); assert_eq!(status.code().unwrap(), 0); let mut child = cmd.spawn(&pts).unwrap(); assert_eq!(output.next().await.unwrap(), "foo\r\n"); let mut buf = vec![]; tokio::time::timeout( std::time::Duration::from_secs(5), stderr_pipe_r.read_until(b'\n', &mut buf), ) .await .unwrap() .unwrap(); assert_eq!(std::string::String::from_utf8(buf).unwrap(), "foo-stderr\n"); let mut buf = vec![]; tokio::time::timeout( std::time::Duration::from_secs(5), pre_exec_pipe_r.read_until(b'\n', &mut buf), ) .await .unwrap() .unwrap(); assert_eq!(std::string::String::from_utf8(buf).unwrap(), "foo-3\n"); let status = child.wait().await.unwrap(); assert_eq!(status.code().unwrap(), 0); } #[test] fn test_controlling_terminal() { let pty = pty_process::blocking::Pty::new().unwrap(); let pts = pty.pts().unwrap(); pty.resize(pty_process::Size::new(24, 80)).unwrap(); let mut child = pty_process::blocking::Command::new("perl") .arg("-Eopen my $fh, '<', '/dev/tty' or die; if (-t $fh) { say 'true' } else { say 'false' }") .spawn(&pts) .unwrap(); let mut output = helpers::output(&pty); assert_eq!(output.next().unwrap(), "true\r\n"); let status = child.wait().unwrap(); assert_eq!(status.code().unwrap(), 0); } #[cfg(feature = "async")] #[tokio::test] async fn test_controlling_terminal_async() { use futures::stream::StreamExt as _; let mut pty = pty_process::Pty::new().unwrap(); let pts = pty.pts().unwrap(); pty.resize(pty_process::Size::new(24, 80)).unwrap(); let (pty_r, _) = pty.split(); let mut child = pty_process::Command::new("perl") .arg( "-Eopen my $fh, '<', '/dev/tty' or die; \ if (-t $fh) { say 'true' } else { say 'false' }", ) .spawn(&pts) .unwrap(); let mut output = helpers::output_async(pty_r); assert_eq!(output.next().await.unwrap(), "true\r\n"); let status = child.wait().await.unwrap(); assert_eq!(status.code().unwrap(), 0); } #[test] fn test_session_leader() { let pty = pty_process::blocking::Pty::new().unwrap(); let pts = pty.pts().unwrap(); pty.resize(pty_process::Size::new(24, 80)).unwrap(); let mut child = pty_process::blocking::Command::new("python") .arg("-cimport os; print(os.getpid() == os.getsid(0))") .spawn(&pts) .unwrap(); let mut output = helpers::output(&pty); assert_eq!(output.next().unwrap(), "True\r\n"); let status = child.wait().unwrap(); assert_eq!(status.code().unwrap(), 0); } #[cfg(feature = "async")] #[tokio::test] async fn test_session_leader_async() { use futures::stream::StreamExt as _; let mut pty = pty_process::Pty::new().unwrap(); let pts = pty.pts().unwrap(); pty.resize(pty_process::Size::new(24, 80)).unwrap(); let mut child = pty_process::Command::new("python") .arg("-cimport os; print(os.getpid() == os.getsid(0))") .spawn(&pts) .unwrap(); let (pty_r, _) = pty.split(); let mut output = helpers::output_async(pty_r); assert_eq!(output.next().await.unwrap(), "True\r\n"); let status = child.wait().await.unwrap(); eprintln!("{:?}", status); assert_eq!(status.code().unwrap(), 0); } fn pipe() -> (std::os::fd::OwnedFd, std::os::fd::OwnedFd) { use std::os::fd::FromRawFd as _; let (r, w) = nix::unistd::pipe().unwrap(); (unsafe { std::os::fd::OwnedFd::from_raw_fd(r) }, unsafe { std::os::fd::OwnedFd::from_raw_fd(w) }) }