Skip to content

Commit

Permalink
feat(cmd): Timeout support
Browse files Browse the repository at this point in the history
We test with `failure` because Linux considers it `interrupted` but not
windows.  `failure` is common to both platforms.

Fixes assert-rs#10
  • Loading branch information
Ed Page committed Mar 26, 2020
1 parent 112bafd commit f6d7541
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 9 deletions.
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
<a name="0.12.2"></a>
## 0.12.2 (2020-03-26)

* **cmd**:
* Support timeouts (closes [#10](https://github.com/assert-rs/assert_cmd/issues/20)).

<a name="0.12.1"></a>
## 0.12.1 (2020-03-25)


#### Bug Fixes

* **stdin**: Avoid stdin/stdout deadlocks by writing/reading in parallel (closes [#42](https://github.com/assert-rs/assert_cmd/issues/42)).
* **cmd**:
* Avoid stdin/stdout deadlocks by writing/reading in parallel (closes [#42](https://github.com/assert-rs/assert_cmd/issues/42)).

<a name="0.12.0"></a>
## 0.12.0 (2019-12-05)
Expand Down
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.failure();
/// ```
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.failure();
}

0 comments on commit f6d7541

Please sign in to comment.