Skip to content

Commit 465e8af

Browse files
authored
Merge pull request #30 from jwodder/wait-all
Ensure all output is read before exiting
2 parents 345bfc1 + 45039ab commit 465e8af

File tree

4 files changed

+65
-37
lines changed

4 files changed

+65
-37
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
v0.2.1 (in development)
2+
-----------------------
3+
- **Bugfix**: Ensure that all output from the child process is read & echoed
4+
before exiting
5+
16
v0.2.0 (2025-11-02)
27
-------------------
38
- Added a `--format` option for setting the format of the status line

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "elapsed-cmd"
3-
version = "0.2.0"
3+
version = "0.2.1-dev"
44
edition = "2024"
55
rust-version = "1.85"
66
description = "Show runtime while a command runs"

src/main.rs

Lines changed: 58 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -281,37 +281,53 @@ struct Elapsing {
281281

282282
impl Elapsing {
283283
async fn event_loop(&mut self) -> Result<ExitCode, Error> {
284+
let mut stdout_eof = false;
285+
let mut stderr_eof = false;
286+
let mut exit_code = None;
284287
loop {
288+
if stdout_eof && stderr_eof {
289+
if let Some(rc) = exit_code {
290+
self.statline.clear()?;
291+
return Ok(rc);
292+
}
293+
}
285294
tokio::select! {
286295
_ = self.ticker.tick() => {
287296
self.statline.clear()?;
288297
self.statline.print()?;
289298
},
290-
r = self.pout.next_line() => {
299+
r = self.pout.next_line(), if !stdout_eof => {
291300
if self.stdout_is_tty {
292301
self.statline.clear()?;
293302
}
294-
let line = r.map_err(Error::ReadStdout)?;
295-
self.stdout.lock().write_all(&line).map_err(Error::Write)?;
303+
if let Some(line) = r.map_err(Error::ReadStdout)? {
304+
self.stdout.lock().write_all(&line).map_err(Error::Write)?;
305+
} else {
306+
stdout_eof = true;
307+
}
296308
if self.stdout_is_tty {
297309
self.statline.print()?;
298310
}
299311
}
300-
r = self.perr.next_line() => {
312+
r = self.perr.next_line(), if !stderr_eof => {
301313
self.statline.clear()?;
302-
let line = r.map_err(Error::ReadStderr)?;
303-
self.stderr.lock().write_all(&line).map_err(Error::Write)?;
314+
if let Some(line) = r.map_err(Error::ReadStderr)? {
315+
self.stderr.lock().write_all(&line).map_err(Error::Write)?;
316+
} else {
317+
stderr_eof = true;
318+
}
304319
self.statline.print()?;
305320
}
306-
r = self.p.wait() => {
321+
r = self.p.wait(), if exit_code.is_none() => {
307322
self.statline.clear()?;
308323
let rc = r.map_err(Error::Wait)?;
309324
if let Some(ret) = rc.code() {
310325
let ret = u8::try_from(ret & 255).unwrap_or(1);
311-
return Ok(ExitCode::from(ret));
326+
exit_code = Some(ExitCode::from(ret));
312327
} else {
313328
return Err(Error::Signal(rc));
314329
}
330+
self.statline.print()?;
315331
}
316332
r = tokio::signal::ctrl_c() => {
317333
if r.is_ok() {
@@ -488,14 +504,14 @@ struct NextLine<'a, R> {
488504
}
489505

490506
impl<R: AsyncRead + Unpin> Future for NextLine<'_, R> {
491-
type Output = io::Result<Vec<u8>>;
507+
type Output = io::Result<Option<Vec<u8>>>;
492508

493509
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
494510
loop {
495511
if let Some(ln) = self.inner.get_line() {
496-
return Poll::Ready(Ok(ln));
512+
return Poll::Ready(Ok(Some(ln)));
497513
} else if self.inner.eof {
498-
return Poll::Pending;
514+
return Poll::Ready(Ok(None));
499515
} else {
500516
let mut buf0 = vec![0u8; READ_BUFFER_SIZE];
501517
let mut buf = ReadBuf::new(&mut buf0);
@@ -649,28 +665,32 @@ mod tests {
649665
mod byte_lines {
650666
use super::*;
651667
use std::io::Cursor;
652-
use tokio_test::{assert_pending, io::Builder, task::spawn};
668+
use tokio_test::io::Builder;
653669

654670
#[tokio::test]
655671
async fn many_short_lines() {
656672
let reader = Cursor::new(b"Hello!\nI like your code.\nGoodbye!\n");
657673
let mut lines = ByteLines::new(reader);
658-
assert_eq!(lines.next_line().await.unwrap(), b"Hello!\n");
659-
assert_eq!(lines.next_line().await.unwrap(), b"I like your code.\n");
660-
assert_eq!(lines.next_line().await.unwrap(), b"Goodbye!\n");
661-
let mut fut = spawn(lines.next_line());
662-
assert_pending!(fut.poll());
674+
assert_eq!(lines.next_line().await.unwrap().unwrap(), b"Hello!\n");
675+
assert_eq!(
676+
lines.next_line().await.unwrap().unwrap(),
677+
b"I like your code.\n"
678+
);
679+
assert_eq!(lines.next_line().await.unwrap().unwrap(), b"Goodbye!\n");
680+
assert_eq!(lines.next_line().await.unwrap(), None);
663681
}
664682

665683
#[tokio::test]
666684
async fn many_short_lines_no_final_newline() {
667685
let reader = Cursor::new(b"Hello!\nI like your code.\nGoodbye!");
668686
let mut lines = ByteLines::new(reader);
669-
assert_eq!(lines.next_line().await.unwrap(), b"Hello!\n");
670-
assert_eq!(lines.next_line().await.unwrap(), b"I like your code.\n");
671-
assert_eq!(lines.next_line().await.unwrap(), b"Goodbye!");
672-
let mut fut = spawn(lines.next_line());
673-
assert_pending!(fut.poll());
687+
assert_eq!(lines.next_line().await.unwrap().unwrap(), b"Hello!\n");
688+
assert_eq!(
689+
lines.next_line().await.unwrap().unwrap(),
690+
b"I like your code.\n"
691+
);
692+
assert_eq!(lines.next_line().await.unwrap().unwrap(), b"Goodbye!");
693+
assert_eq!(lines.next_line().await.unwrap(), None);
674694
}
675695

676696
#[tokio::test]
@@ -681,32 +701,35 @@ mod tests {
681701
.read(b"Bye now!\n")
682702
.build();
683703
let mut lines = ByteLines::new(reader);
684-
assert_eq!(lines.next_line().await.unwrap(), b"Hello, World!\n");
685-
assert_eq!(lines.next_line().await.unwrap(), b"Bye now!\n");
686-
let mut fut = spawn(lines.next_line());
687-
assert_pending!(fut.poll());
704+
assert_eq!(
705+
lines.next_line().await.unwrap().unwrap(),
706+
b"Hello, World!\n"
707+
);
708+
assert_eq!(lines.next_line().await.unwrap().unwrap(), b"Bye now!\n");
709+
assert_eq!(lines.next_line().await.unwrap(), None);
688710
}
689711

690712
#[tokio::test]
691713
async fn non_utf8() {
692714
let reader = Cursor::new(b"Hell\xF6!\nI like your code.\nGoodbye!\n");
693715
let mut lines = ByteLines::new(reader);
694-
assert_eq!(lines.next_line().await.unwrap(), b"Hell\xF6!\n");
695-
assert_eq!(lines.next_line().await.unwrap(), b"I like your code.\n");
696-
assert_eq!(lines.next_line().await.unwrap(), b"Goodbye!\n");
697-
let mut fut = spawn(lines.next_line());
698-
assert_pending!(fut.poll());
716+
assert_eq!(lines.next_line().await.unwrap().unwrap(), b"Hell\xF6!\n");
717+
assert_eq!(
718+
lines.next_line().await.unwrap().unwrap(),
719+
b"I like your code.\n"
720+
);
721+
assert_eq!(lines.next_line().await.unwrap().unwrap(), b"Goodbye!\n");
722+
assert_eq!(lines.next_line().await.unwrap(), None);
699723
}
700724

701725
#[tokio::test]
702726
async fn strip_cr() {
703727
let reader = Cursor::new(b"Hello!\r\nGoodbye!\n");
704728
let mut lines = ByteLines::new(reader);
705729
lines.strip_cr = true;
706-
assert_eq!(lines.next_line().await.unwrap(), b"Hello!\n");
707-
assert_eq!(lines.next_line().await.unwrap(), b"Goodbye!\n");
708-
let mut fut = spawn(lines.next_line());
709-
assert_pending!(fut.poll());
730+
assert_eq!(lines.next_line().await.unwrap().unwrap(), b"Hello!\n");
731+
assert_eq!(lines.next_line().await.unwrap().unwrap(), b"Goodbye!\n");
732+
assert_eq!(lines.next_line().await.unwrap(), None);
710733
}
711734
}
712735
}

0 commit comments

Comments
 (0)