From d1a9c4d1d878e7bcb4caac766fabcfb48fe418f2 Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Thu, 30 Dec 2021 15:43:21 -0500 Subject: improve tests --- tests/basic.rs | 107 ++++++--------------------------------- tests/behavior.rs | 138 +++++++++++++++++++++++++++++++++++++++++++++++++++ tests/fds.rs | 22 ++++---- tests/fds_async.rs | 36 +++++++------- tests/helpers/mod.rs | 56 +++++++++++++++++++++ tests/multiple.rs | 58 ---------------------- tests/winch.rs | 32 ++++++------ 7 files changed, 251 insertions(+), 198 deletions(-) create mode 100644 tests/behavior.rs create mode 100644 tests/helpers/mod.rs delete mode 100644 tests/multiple.rs (limited to 'tests') diff --git a/tests/basic.rs b/tests/basic.rs index f73b39e..cab2c16 100644 --- a/tests/basic.rs +++ b/tests/basic.rs @@ -1,27 +1,22 @@ +mod helpers; + #[test] fn test_cat_blocking() { - use std::io::{Read as _, Write as _}; + use std::io::Write as _; - let mut pty = pty_process::blocking::Pty::new().unwrap(); + let pty = pty_process::blocking::Pty::new().unwrap(); pty.resize(pty_process::Size::new(24, 80)).unwrap(); let mut child = pty_process::blocking::Command::new("cat") .spawn(&pty) .unwrap(); - pty.write_all(b"foo\n").unwrap(); - // the pty will echo the written bytes back immediately, but the - // subprocess needs to generate its own output, which takes time, so we - // can't just read immediately (we may just get the echoed bytes). because - // the output generation is happening in the subprocess, we also don't - // have any way to know when (or if!) the subprocess will decide to send - // its output, so sleeping is the best we can do. - std::thread::sleep(std::time::Duration::from_secs(1)); + (&pty).write_all(b"foo\n").unwrap(); - let mut buf = [0u8; 1024]; - let bytes = pty.read(&mut buf).unwrap(); - assert_eq!(&buf[..bytes], b"foo\r\nfoo\r\n"); + let mut output = helpers::output(&pty); + assert_eq!(output.next().unwrap(), "foo\r\n"); + assert_eq!(output.next().unwrap(), "foo\r\n"); - pty.write_all(&[4u8]).unwrap(); + (&pty).write_all(&[4u8]).unwrap(); let status = child.wait().unwrap(); assert_eq!(status.code().unwrap(), 0); } @@ -30,91 +25,21 @@ fn test_cat_blocking() { #[test] fn test_cat_async_std() { use async_std::io::prelude::WriteExt as _; - use async_std::io::ReadExt as _; + use futures::stream::StreamExt as _; let status = async_std::task::block_on(async { - let mut pty = pty_process::Pty::new().unwrap(); - pty.resize(pty_process::Size::new(24, 80)).unwrap(); - let mut child = pty_process::Command::new("cat").spawn(&pty).unwrap(); - - pty.write_all(b"foo\n").await.unwrap(); - // the pty will echo the written bytes back immediately, but the - // subprocess needs to generate its own output, which takes time, so - // we can't just read immediately (we may just get the echoed bytes). - // because the output generation is happening in the subprocess, we - // also don't have any way to know when (or if!) the subprocess will - // decide to send its output, so sleeping is the best we can do. - async_std::task::sleep(std::time::Duration::from_secs(1)).await; - - let mut buf = [0u8; 1024]; - let bytes = pty.read(&mut buf).await.unwrap(); - assert_eq!(&buf[..bytes], b"foo\r\nfoo\r\n"); - - pty.write_all(&[4u8]).await.unwrap(); - child.status().await.unwrap() - }); - assert_eq!(status.code().unwrap(), 0); -} - -#[cfg(feature = "async")] -#[test] -fn test_cat_smol() { - use smol::io::{AsyncReadExt as _, AsyncWriteExt as _}; - - let status = smol::block_on(async { - let mut pty = pty_process::Pty::new().unwrap(); + let pty = pty_process::Pty::new().unwrap(); pty.resize(pty_process::Size::new(24, 80)).unwrap(); let mut child = pty_process::Command::new("cat").spawn(&pty).unwrap(); - pty.write_all(b"foo\n").await.unwrap(); - // the pty will echo the written bytes back immediately, but the - // subprocess needs to generate its own output, which takes time, so - // we can't just read immediately (we may just get the echoed bytes). - // because the output generation is happening in the subprocess, we - // also don't have any way to know when (or if!) the subprocess will - // decide to send its output, so sleeping is the best we can do. - smol::Timer::after(std::time::Duration::from_secs(1)).await; + (&pty).write_all(b"foo\n").await.unwrap(); - let mut buf = [0u8; 1024]; - let bytes = pty.read(&mut buf).await.unwrap(); - assert_eq!(&buf[..bytes], b"foo\r\nfoo\r\n"); + let mut output = helpers::output_async(&pty); + assert_eq!(output.next().await.unwrap(), "foo\r\n"); + assert_eq!(output.next().await.unwrap(), "foo\r\n"); - pty.write_all(&[4u8]).await.unwrap(); + (&pty).write_all(&[4u8]).await.unwrap(); child.status().await.unwrap() }); assert_eq!(status.code().unwrap(), 0); } - -#[cfg(feature = "async")] -#[test] -fn test_cat_tokio() { - use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _}; - use tokio_util::compat::FuturesAsyncReadCompatExt as _; - - async fn async_test_cat_tokio() { - let pty = pty_process::Pty::new().unwrap(); - pty.resize(pty_process::Size::new(24, 80)).unwrap(); - let mut child = pty_process::Command::new("cat").spawn(&pty).unwrap(); - - pty.compat().write_all(b"foo\n").await.unwrap(); - // the pty will echo the written bytes back immediately, but the - // subprocess needs to generate its own output, which takes time, so - // we can't just read immediately (we may just get the echoed bytes). - // because the output generation is happening in the subprocess, we - // also don't have any way to know when (or if!) the subprocess will - // decide to send its output, so sleeping is the best we can do. - tokio::time::sleep(std::time::Duration::from_secs(1)).await; - - let mut buf = [0u8; 1024]; - let bytes = pty.compat().read(&mut buf).await.unwrap(); - assert_eq!(&buf[..bytes], b"foo\r\nfoo\r\n"); - - pty.compat().write_all(&[4u8]).await.unwrap(); - - let status = child.status().await.unwrap(); - assert_eq!(status.code().unwrap(), 0); - } - tokio::runtime::Runtime::new().unwrap().block_on(async { - async_test_cat_tokio().await; - }); -} diff --git a/tests/behavior.rs b/tests/behavior.rs new file mode 100644 index 0000000..ef7d54f --- /dev/null +++ b/tests/behavior.rs @@ -0,0 +1,138 @@ +mod helpers; + +#[test] +fn test_multiple() { + let pty = pty_process::blocking::Pty::new().unwrap(); + pty.resize(pty_process::Size::new(24, 80)).unwrap(); + + let mut child = pty_process::blocking::Command::new("echo") + .arg("foo") + .spawn(&pty) + .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(&pty) + .unwrap(); + + let mut output = helpers::output(&pty); + assert_eq!(output.next().unwrap(), "bar\r\n"); + + let status = child.wait().unwrap(); + assert_eq!(status.code().unwrap(), 0); +} + +#[cfg(feature = "async")] +#[test] +fn test_multiple_async() { + use futures::stream::StreamExt as _; + + async_std::task::block_on(async { + let pty = pty_process::Pty::new().unwrap(); + pty.resize(pty_process::Size::new(24, 80)).unwrap(); + + let mut child = pty_process::Command::new("echo") + .arg("foo") + .spawn(&pty) + .unwrap(); + + let mut output = helpers::output_async(&pty); + assert_eq!(output.next().await.unwrap(), "foo\r\n"); + + let status = child.status().await.unwrap(); + assert_eq!(status.code().unwrap(), 0); + + let mut child = pty_process::Command::new("echo") + .arg("bar") + .spawn(&pty) + .unwrap(); + + let mut output = helpers::output_async(&pty); + assert_eq!(output.next().await.unwrap(), "bar\r\n"); + + let status = child.status().await.unwrap(); + assert_eq!(status.code().unwrap(), 0); + }); +} + +#[test] +fn test_controlling_terminal() { + let pty = pty_process::blocking::Pty::new().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(&pty) + .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")] +#[test] +fn test_controlling_terminal_async() { + use futures::stream::StreamExt as _; + + async_std::task::block_on(async { + let pty = pty_process::Pty::new().unwrap(); + pty.resize(pty_process::Size::new(24, 80)).unwrap(); + 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(&pty) + .unwrap(); + + let mut output = helpers::output_async(&pty); + assert_eq!(output.next().await.unwrap(), "true\r\n"); + + let status = child.status().await.unwrap(); + assert_eq!(status.code().unwrap(), 0); + }); +} + +#[test] +fn test_session_leader() { + let pty = pty_process::blocking::Pty::new().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(&pty) + .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")] +#[test] +fn test_session_leader_async() { + use futures::stream::StreamExt as _; + + async_std::task::block_on(async { + let pty = pty_process::Pty::new().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(&pty) + .unwrap(); + + { + let mut output = helpers::output_async(&pty); + assert_eq!(output.next().await.unwrap(), "True\r\n"); + } + + let status = child.status().await.unwrap(); + assert_eq!(status.code().unwrap(), 0); + }); +} diff --git a/tests/fds.rs b/tests/fds.rs index 7ef802d..8a6bb19 100644 --- a/tests/fds.rs +++ b/tests/fds.rs @@ -1,7 +1,7 @@ +mod helpers; + #[test] fn test_fds() { - use std::io::BufRead as _; - check_open_fds(); let pty = pty_process::blocking::Pty::new().unwrap(); @@ -10,11 +10,10 @@ fn test_fds() { .arg("-Efor my $fd (0..255) { open my $fh, \"<&=$fd\"; print $fd if stat $fh }; say") .spawn(&pty) .unwrap(); - let mut buf = vec![]; - std::io::BufReader::new(&pty) - .read_until(b'\n', &mut buf) - .unwrap(); - assert_eq!(&buf, b"012\r\n"); + + let mut output = helpers::output(&pty); + assert_eq!(output.next().unwrap(), "012\r\n"); + let status = child.wait().unwrap(); assert_eq!(status.code().unwrap(), 0); drop(pty); @@ -27,11 +26,10 @@ fn test_fds() { .stderr(Some(std::process::Stdio::null())) .spawn(&pty) .unwrap(); - let mut buf = vec![]; - std::io::BufReader::new(&pty) - .read_until(b'\n', &mut buf) - .unwrap(); - assert_eq!(&buf, b"012\r\n"); + + let mut output = helpers::output(&pty); + assert_eq!(output.next().unwrap(), "012\r\n"); + let status = child.wait().unwrap(); assert_eq!(status.code().unwrap(), 0); drop(pty); diff --git a/tests/fds_async.rs b/tests/fds_async.rs index e2c369d..7710789 100644 --- a/tests/fds_async.rs +++ b/tests/fds_async.rs @@ -1,7 +1,9 @@ +mod helpers; + #[cfg(feature = "async")] #[test] fn test_fds_async() { - use async_std::io::prelude::BufReadExt as _; + use futures::stream::StreamExt as _; check_open_fds(&[0, 1, 2]); @@ -14,12 +16,10 @@ fn test_fds_async() { .arg("-Efor my $fd (0..255) { open my $fh, \"<&=$fd\"; print $fd if stat $fh }; say") .spawn(&pty) .unwrap(); - let mut buf = vec![]; - async_std::io::BufReader::new(&pty) - .read_until(b'\n', &mut buf) - .await - .unwrap(); - assert_eq!(&buf, b"012\r\n"); + + let mut output = helpers::output_async(&pty); + assert_eq!(output.next().await.unwrap(), "012\r\n"); + let status = child.status().await.unwrap(); assert_eq!(status.code().unwrap(), 0); }); @@ -33,14 +33,13 @@ fn test_fds_async() { .arg("-Efor my $fd (0..255) { open my $fh, \"<&=$fd\"; print $fd if stat $fh }; say") .spawn(&pty) .unwrap(); - let mut buf = vec![]; - async_std::io::BufReader::new(&pty) - .read_until(b'\n', &mut buf) - .await - .unwrap(); - assert_eq!(&buf, b"012\r\n"); + + let mut output = helpers::output_async(&pty); + assert_eq!(output.next().await.unwrap(), "012\r\n"); + let status = child.status().await.unwrap(); assert_eq!(status.code().unwrap(), 0); + drop(output); drop(pty); check_open_fds(&fds); @@ -56,14 +55,13 @@ fn test_fds_async() { .stderr(Some(std::process::Stdio::null())) .spawn(&pty) .unwrap(); - let mut buf = vec![]; - async_std::io::BufReader::new(&pty) - .read_until(b'\n', &mut buf) - .await - .unwrap(); - assert_eq!(&buf, b"012\r\n"); + + let mut output = helpers::output_async(&pty); + assert_eq!(output.next().await.unwrap(), "012\r\n"); + let status = child.status().await.unwrap(); assert_eq!(status.code().unwrap(), 0); + drop(output); drop(pty); check_open_fds(&fds); diff --git a/tests/helpers/mod.rs b/tests/helpers/mod.rs new file mode 100644 index 0000000..4fee8df --- /dev/null +++ b/tests/helpers/mod.rs @@ -0,0 +1,56 @@ +#![allow(dead_code)] + +use std::io::BufRead as _; + +pub struct Output<'a> { + pty: std::io::BufReader<&'a pty_process::blocking::Pty>, +} + +impl<'a> Output<'a> { + fn new(pty: &'a pty_process::blocking::Pty) -> Self { + Self { + pty: std::io::BufReader::new(pty), + } + } +} + +impl<'a> Iterator for Output<'a> { + type Item = String; + + fn next(&mut self) -> Option { + let mut buf = vec![]; + nix::unistd::alarm::set(5); + self.pty.read_until(b'\n', &mut buf).unwrap(); + nix::unistd::alarm::cancel(); + Some(std::string::String::from_utf8(buf).unwrap()) + } +} + +pub fn output(pty: &pty_process::blocking::Pty) -> Output<'_> { + Output::new(pty) +} + +#[cfg(feature = "async")] +pub fn output_async( + pty: &pty_process::Pty, +) -> std::pin::Pin + '_>> { + use async_std::io::prelude::BufReadExt as _; + use futures::FutureExt as _; + + let pty = async_std::io::BufReader::new(pty); + Box::pin(futures::stream::unfold(pty, |mut pty| async move { + Some(( + async_std::future::timeout( + std::time::Duration::from_secs(5), + async { + let mut buf = vec![]; + pty.read_until(b'\n', &mut buf).await.unwrap(); + std::string::String::from_utf8(buf).unwrap() + }, + ) + .map(|x| x.unwrap()) + .await, + pty, + )) + })) +} diff --git a/tests/multiple.rs b/tests/multiple.rs deleted file mode 100644 index 7e27512..0000000 --- a/tests/multiple.rs +++ /dev/null @@ -1,58 +0,0 @@ -#[test] -fn test_multiple() { - use std::io::Read as _; - - let mut pty = pty_process::blocking::Pty::new().unwrap(); - pty.resize(pty_process::Size::new(24, 80)).unwrap(); - - let mut child = pty_process::blocking::Command::new("echo") - .arg("foo") - .spawn(&pty) - .unwrap(); - let mut buf = [0u8; 1024]; - let bytes = pty.read(&mut buf).unwrap(); - assert_eq!(&buf[..bytes], b"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(&pty) - .unwrap(); - let mut buf = [0u8; 1024]; - let bytes = pty.read(&mut buf).unwrap(); - assert_eq!(&buf[..bytes], b"bar\r\n"); - let status = child.wait().unwrap(); - assert_eq!(status.code().unwrap(), 0); -} - -#[cfg(feature = "async")] -#[test] -fn test_multiple_async() { - use async_std::io::ReadExt as _; - - async_std::task::block_on(async { - let mut pty = pty_process::Pty::new().unwrap(); - pty.resize(pty_process::Size::new(24, 80)).unwrap(); - - let mut child = pty_process::Command::new("echo") - .arg("foo") - .spawn(&pty) - .unwrap(); - let mut buf = [0u8; 1024]; - let bytes = pty.read(&mut buf).await.unwrap(); - assert_eq!(&buf[..bytes], b"foo\r\n"); - let status = child.status().await.unwrap(); - assert_eq!(status.code().unwrap(), 0); - - let mut child = pty_process::Command::new("echo") - .arg("bar") - .spawn(&pty) - .unwrap(); - let mut buf = [0u8; 1024]; - let bytes = pty.read(&mut buf).await.unwrap(); - assert_eq!(&buf[..bytes], b"bar\r\n"); - let status = child.status().await.unwrap(); - assert_eq!(status.code().unwrap(), 0); - }); -} diff --git a/tests/winch.rs b/tests/winch.rs index a189697..50447cb 100644 --- a/tests/winch.rs +++ b/tests/winch.rs @@ -1,8 +1,10 @@ +mod helpers; + #[test] fn test_winch_std() { - use std::io::{Read as _, Write as _}; + use std::io::Write as _; - let mut pty = pty_process::blocking::Pty::new().unwrap(); + let pty = pty_process::blocking::Pty::new().unwrap(); pty.resize(pty_process::Size::new(24, 80)).unwrap(); let mut child = pty_process::blocking::Command::new("perl") .args(&[ @@ -12,16 +14,13 @@ fn test_winch_std() { .spawn(&pty) .unwrap(); - let mut buf = [0u8; 1024]; - let bytes = pty.read(&mut buf).unwrap(); - assert_eq!(&buf[..bytes], b"started\r\n"); + let mut output = helpers::output(&pty); + assert_eq!(output.next().unwrap(), "started\r\n"); pty.resize(pty_process::Size::new(25, 80)).unwrap(); + assert_eq!(output.next().unwrap(), "WINCH\r\n"); - let bytes = pty.read(&mut buf).unwrap(); - assert_eq!(&buf[..bytes], b"WINCH\r\n"); - - pty.write_all(b"\n").unwrap(); + (&pty).write_all(b"\n").unwrap(); let status = child.wait().unwrap(); assert_eq!(status.code().unwrap(), 0); } @@ -30,10 +29,10 @@ fn test_winch_std() { #[test] fn test_winch_async() { use async_std::io::prelude::WriteExt as _; - use async_std::io::ReadExt as _; + use futures::stream::StreamExt as _; let status = async_std::task::block_on(async { - let mut pty = pty_process::Pty::new().unwrap(); + let pty = pty_process::Pty::new().unwrap(); pty.resize(pty_process::Size::new(24, 80)).unwrap(); let mut child = pty_process::Command::new("perl") .args(&[ @@ -43,16 +42,13 @@ fn test_winch_async() { .spawn(&pty) .unwrap(); - let mut buf = [0u8; 1024]; - let bytes = pty.read(&mut buf).await.unwrap(); - assert_eq!(&buf[..bytes], b"started\r\n"); + let mut output = helpers::output_async(&pty); + assert_eq!(output.next().await.unwrap(), "started\r\n"); pty.resize(pty_process::Size::new(25, 80)).unwrap(); + assert_eq!(output.next().await.unwrap(), "WINCH\r\n"); - let bytes = pty.read(&mut buf).await.unwrap(); - assert_eq!(&buf[..bytes], b"WINCH\r\n"); - - pty.write_all(b"\n").await.unwrap(); + (&pty).write_all(b"\n").await.unwrap(); child.status().await.unwrap() }); assert_eq!(status.code().unwrap(), 0); -- cgit v1.2.3-54-g00ecf