Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

session: add expect_eager method #35

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions src/session/async_session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,29 @@ impl<P, S: AsyncRead + Unpin> Session<P, S> {
self.stream.expect(needle).await
}

/// Expect waits until a pattern is matched. This continuously reads all the
/// available output, consumes it from the buffer and try to match on it
/// until a match is found.
///
/// Typically, you'd want to use `expect_eager` over `expect` when the
/// command under test produces too much output to be processed lazily.
///
/// If the method returns [Ok] it is guaranteed that at least 1 match was found.
///
/// ```
/// # futures_lite::future::block_on(async {
/// let mut p = expectrl::spawn("echo 123").unwrap();
/// let m = p.expect_eager(expectrl::Regex("\\d+")).await.unwrap();
/// assert_eq!(m.first(), b"123");
/// # })
/// ```
///
/// It return an error if timeout is reached.
/// You can specify a timeout value by [Session::set_expect_timeout] method.
pub async fn expect_eager<E: Needle>(&mut self, expect: E) -> Result<Found, Error> {
self.stream.expect_eager(needle).await
}

/// Check checks if a pattern is matched.
/// Returns empty found structure if nothing found.
///
Expand Down Expand Up @@ -327,6 +350,34 @@ impl<S: AsyncRead + Unpin> Stream<S> {
}
}

async fn expect_eager<N: Needle>(&mut self, needle: N) -> Result<Captures, Error> {
let start = time::Instant::now();
loop {
if let Some(timeout) = self.expect_timeout {
if start.elapsed() > timeout {
return Err(Error::ExpectTimeout);
}
}

let eof = self.stream.read_available().await?;
let data = self.stream.get_available();

let found = expect.check(data, eof)?;
if !found.is_empty() {
let end_index = Found::right_most_index(&found);
let involved_bytes = data[..end_index].to_vec();
self.stream.consume_from_buffer(end_index);
return Ok(Found::new(involved_bytes, found));
} else if !data.is_empty() {
let data_len = data.len();
self.stream.consume_from_buffer(data_len);
continue;
Comment on lines +371 to +374
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this what you wan't?

I mean potentially a mach will never be found because we drop data here.

Here's an example.

The output Hello World.
We do expect_eager("Hello World")
First read_available reads only Hello,
Second read_available reads World.
Because you drop the buffer the match will never found even though it was there.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, good point! I guess we should never drain the buffer for this approach

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking, maybe we could still drain the unmatched buffer parts up to line breaks that would keep the buffer from growing until the match is found, but only if the Needle itself doesn't contain a line break

} else if eof {
return Err(Error::Eof);
}
}
}

/// Is matched checks if a pattern is matched.
/// It doesn't consumes bytes from stream.
async fn is_matched<E: Needle>(&mut self, needle: E) -> Result<bool, Error> {
Expand Down
45 changes: 45 additions & 0 deletions src/session/sync_session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,51 @@ impl<P, S: Read + NonBlocking> Session<P, S> {
}
}

/// Expect waits until a pattern is matched. This continuously reads all the
/// available output, consumes it from the buffer and try to match on it
/// until a match is found.
///
/// Typically, you'd want to use `expect_eager` over `expect` when the
/// command under test produces too much output to be processed lazily.
///
/// If the method returns [Ok] it is guaranteed that at least 1 match was found.
///
/// ```
/// let mut p = expectrl::spawn("echo 123").unwrap();
/// let m = p.expect_eager(expectrl::Regex("\\d+")).unwrap();
/// assert_eq!(m.first(), b"123");
/// ```
///
/// It returns an error if timeout is reached.
/// You can specify a timeout value by [Session::set_expect_timeout] method.
pub fn expect_eager<E: Needle>(&mut self, expect: E) -> Result<Captures, Error> {
let start = time::Instant::now();
loop {
if let Some(timeout) = self.expect_timeout {
if start.elapsed() > timeout {
return Err(Error::ExpectTimeout);
}
}

let eof = self.stream.read_available()?;
let data = self.stream.get_available();

let found = expect.check(data, eof)?;
if !found.is_empty() {
let end_index = Captures::right_most_index(&found);
let involved_bytes = data[..end_index].to_vec();
self.stream.consume_available(end_index);
return Ok(Captures::new(involved_bytes, found));
} else if !data.is_empty() {
let data_len = data.len();
self.stream.consume_available(data_len);
continue;
} else if eof {
return Err(Error::Eof);
}
}
}

/// Check verifies if a pattern is matched.
/// Returns empty found structure if nothing found.
///
Expand Down