Skip to content

Commit

Permalink
feat(cmd): Timeout support
Browse files Browse the repository at this point in the history
  • Loading branch information
Ed Page committed Mar 26, 2020
1 parent 112bafd commit a69c72a
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 8 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ predicates-core = "1.0"
predicates-tree = "1.0"
escargot = "0.5"
doc-comment = "0.3"
wait-timeout = "0.2.0"
4 changes: 4 additions & 0 deletions src/bin/bin_fixture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ fn run() -> Result<(), Box<dyn Error>> {
eprintln!("{}", text);
}

if let Some(timeout) = env::var("sleep").ok().and_then(|s| s.parse().ok()) {
std::thread::sleep(std::time::Duration::from_secs(timeout));
}

let code = env::var("exit")
.ok()
.map(|v| v.parse::<i32>())
Expand Down
53 changes: 45 additions & 8 deletions src/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,17 @@ use crate::output::OutputResult;
pub struct Command {
cmd: process::Command,
stdin: Option<Vec<u8>>,
timeout: Option<std::time::Duration>,
}

impl Command {
/// Constructs a new `Command` from a `std` `Command`.
pub fn from_std(cmd: process::Command) -> Self {
Self { cmd, stdin: None }
Self {
cmd,
stdin: None,
timeout: None,
}
}

/// Create a `Command` to run a specific binary of the current crate.
Expand Down Expand Up @@ -82,6 +87,23 @@ impl Command {
self
}

/// Error out if a timeout is reached
///
/// ```rust,no_run
/// use assert_cmd::Command;
///
/// let assert = Command::cargo_bin("bin_fixture")
/// .unwrap()
/// .timeout(std::time::Duration::from_secs(1))
/// .env("sleep", "100")
/// .assert();
/// assert.interrupted();
/// ```
pub fn timeout(&mut self, timeout: std::time::Duration) -> &mut Self {
self.timeout = Some(timeout);
self
}

/// Write `path`s content to `stdin` when the `Command` is run.
///
/// Paths are relative to the [`env::current_dir`][env_current_dir] and not
Expand Down Expand Up @@ -417,7 +439,7 @@ impl Command {
/// ```
pub fn output(&mut self) -> io::Result<process::Output> {
let spawn = self.spawn()?;
Self::wait_with_input_output(spawn, self.stdin.clone())
Self::wait_with_input_output(spawn, self.stdin.clone(), self.timeout)
}

/// If `input`, write it to `child`'s stdin while also reading `child`'s
Expand All @@ -428,6 +450,7 @@ impl Command {
fn wait_with_input_output(
mut child: process::Child,
input: Option<Vec<u8>>,
timeout: Option<std::time::Duration>,
) -> io::Result<process::Output> {
let stdin = input.and_then(|i| {
child
Expand All @@ -449,14 +472,28 @@ impl Command {

// Finish writing stdin before waiting, because waiting drops stdin.
stdin.and_then(|t| t.join().unwrap().ok());
let status = child.wait()?;
let stdout = stdout.and_then(|t| t.join().unwrap().ok());
let stderr = stderr.and_then(|t| t.join().unwrap().ok());
let status = if let Some(timeout) = timeout {
wait_timeout::ChildExt::wait_timeout(&mut child, timeout)
.transpose()
.unwrap_or_else(|| {
let _ = child.kill();
child.wait()
})
} else {
child.wait()
}?;

let stdout = stdout
.and_then(|t| t.join().unwrap().ok())
.unwrap_or_default();
let stderr = stderr
.and_then(|t| t.join().unwrap().ok())
.unwrap_or_default();

Ok(process::Output {
status: status,
stdout: stdout.unwrap_or_default(),
stderr: stderr.unwrap_or_default(),
status,
stdout,
stderr,
})
}

Expand Down
12 changes: 12 additions & 0 deletions tests/examples.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,15 @@ fn lib_example() {
.assert();
assert.failure().code(42).stdout("hello\n");
}

#[test]
fn timeout_example() {
use assert_cmd::Command;

let assert = Command::cargo_bin("bin_fixture")
.unwrap()
.timeout(std::time::Duration::from_secs(1))
.env("sleep", "100")
.assert();
assert.interrupted();
}

0 comments on commit a69c72a

Please sign in to comment.