Skip to content

Commit f6d7541

Browse files
author
Ed Page
committed
feat(cmd): Timeout support
We test with `failure` because Linux considers it `interrupted` but not windows. `failure` is common to both platforms. Fixes #10
1 parent 112bafd commit f6d7541

File tree

5 files changed

+70
-9
lines changed

5 files changed

+70
-9
lines changed

CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
1+
<a name="0.12.2"></a>
2+
## 0.12.2 (2020-03-26)
3+
4+
* **cmd**:
5+
* Support timeouts (closes [#10](https://github.com/assert-rs/assert_cmd/issues/20)).
6+
17
<a name="0.12.1"></a>
28
## 0.12.1 (2020-03-25)
39

410

511
#### Bug Fixes
612

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

916
<a name="0.12.0"></a>
1017
## 0.12.0 (2019-12-05)

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ predicates-core = "1.0"
2121
predicates-tree = "1.0"
2222
escargot = "0.5"
2323
doc-comment = "0.3"
24+
wait-timeout = "0.2.0"

src/bin/bin_fixture.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ fn run() -> Result<(), Box<dyn Error>> {
1212
eprintln!("{}", text);
1313
}
1414

15+
if let Some(timeout) = env::var("sleep").ok().and_then(|s| s.parse().ok()) {
16+
std::thread::sleep(std::time::Duration::from_secs(timeout));
17+
}
18+
1519
let code = env::var("exit")
1620
.ok()
1721
.map(|v| v.parse::<i32>())

src/cmd.rs

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,17 @@ use crate::output::OutputResult;
2323
pub struct Command {
2424
cmd: process::Command,
2525
stdin: Option<Vec<u8>>,
26+
timeout: Option<std::time::Duration>,
2627
}
2728

2829
impl Command {
2930
/// Constructs a new `Command` from a `std` `Command`.
3031
pub fn from_std(cmd: process::Command) -> Self {
31-
Self { cmd, stdin: None }
32+
Self {
33+
cmd,
34+
stdin: None,
35+
timeout: None,
36+
}
3237
}
3338

3439
/// Create a `Command` to run a specific binary of the current crate.
@@ -82,6 +87,23 @@ impl Command {
8287
self
8388
}
8489

90+
/// Error out if a timeout is reached
91+
///
92+
/// ```rust,no_run
93+
/// use assert_cmd::Command;
94+
///
95+
/// let assert = Command::cargo_bin("bin_fixture")
96+
/// .unwrap()
97+
/// .timeout(std::time::Duration::from_secs(1))
98+
/// .env("sleep", "100")
99+
/// .assert();
100+
/// assert.failure();
101+
/// ```
102+
pub fn timeout(&mut self, timeout: std::time::Duration) -> &mut Self {
103+
self.timeout = Some(timeout);
104+
self
105+
}
106+
85107
/// Write `path`s content to `stdin` when the `Command` is run.
86108
///
87109
/// Paths are relative to the [`env::current_dir`][env_current_dir] and not
@@ -417,7 +439,7 @@ impl Command {
417439
/// ```
418440
pub fn output(&mut self) -> io::Result<process::Output> {
419441
let spawn = self.spawn()?;
420-
Self::wait_with_input_output(spawn, self.stdin.clone())
442+
Self::wait_with_input_output(spawn, self.stdin.clone(), self.timeout)
421443
}
422444

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

450473
// Finish writing stdin before waiting, because waiting drops stdin.
451474
stdin.and_then(|t| t.join().unwrap().ok());
452-
let status = child.wait()?;
453-
let stdout = stdout.and_then(|t| t.join().unwrap().ok());
454-
let stderr = stderr.and_then(|t| t.join().unwrap().ok());
475+
let status = if let Some(timeout) = timeout {
476+
wait_timeout::ChildExt::wait_timeout(&mut child, timeout)
477+
.transpose()
478+
.unwrap_or_else(|| {
479+
let _ = child.kill();
480+
child.wait()
481+
})
482+
} else {
483+
child.wait()
484+
}?;
485+
486+
let stdout = stdout
487+
.and_then(|t| t.join().unwrap().ok())
488+
.unwrap_or_default();
489+
let stderr = stderr
490+
.and_then(|t| t.join().unwrap().ok())
491+
.unwrap_or_default();
455492

456493
Ok(process::Output {
457-
status: status,
458-
stdout: stdout.unwrap_or_default(),
459-
stderr: stderr.unwrap_or_default(),
494+
status,
495+
stdout,
496+
stderr,
460497
})
461498
}
462499

tests/examples.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,15 @@ fn lib_example() {
1414
.assert();
1515
assert.failure().code(42).stdout("hello\n");
1616
}
17+
18+
#[test]
19+
fn timeout_example() {
20+
use assert_cmd::Command;
21+
22+
let assert = Command::cargo_bin("bin_fixture")
23+
.unwrap()
24+
.timeout(std::time::Duration::from_secs(1))
25+
.env("sleep", "100")
26+
.assert();
27+
assert.failure();
28+
}

0 commit comments

Comments
 (0)