Skip to content

Commit df7ba77

Browse files
committed
Added predicates.
1 parent 5b3adfd commit df7ba77

6 files changed

Lines changed: 143 additions & 11 deletions

File tree

Cargo.lock

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "cli-assert"
3-
version = "0.1.12"
3+
version = "0.1.12-rc.0"
44
authors = ["Dariusz Depta <depta@engos.de>"]
55
description = "Testing command-line applications"
66
documentation = "https://docs.rs/cli-assert"
@@ -18,4 +18,4 @@ exclude = [
1818
]
1919

2020
[dependencies]
21-
tempfile = "3.24.0"
21+
tempfile = "3.26.0"

src/assertions.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
use crate::utils::bytes_to_str;
22

3+
type StatusPredicate = Box<dyn Fn(i32) -> bool>;
4+
5+
type StdPredicate = Box<dyn Fn(Vec<u8>) -> bool>;
6+
37
#[derive(Default)]
48
pub struct Assertions {
59
/// Flag indicating if the command is expected to complete successfully.
@@ -8,10 +12,16 @@ pub struct Assertions {
812
failure: Option<bool>,
913
/// Expected status code.
1014
status: Option<i32>,
15+
/// Predicate for status code.
16+
status_predicate: Option<StatusPredicate>,
1117
/// Expected content of `stdout` after executing the command.
1218
stdout: Option<Vec<u8>>,
19+
/// Predicate for stdout.
20+
stdout_predicate: Option<StdPredicate>,
1321
/// Expected content of `stderr` after executing the command.
1422
stderr: Option<Vec<u8>>,
23+
/// Predicate for stderr.
24+
stderr_predicate: Option<StdPredicate>,
1525
}
1626

1727
impl Assertions {
@@ -29,14 +39,26 @@ impl Assertions {
2939
self.status = Some(code);
3040
}
3141

42+
pub fn code_fn(&mut self, predicate: impl Fn(i32) -> bool + 'static) {
43+
self.status_predicate = Some(Box::new(predicate));
44+
}
45+
3246
pub fn stdout(&mut self, bytes: impl AsRef<[u8]>) {
3347
self.stdout = Some(bytes.as_ref().to_vec());
3448
}
3549

50+
pub fn stdout_fn(&mut self, predicate: impl Fn(Vec<u8>) -> bool + 'static) {
51+
self.stdout_predicate = Some(Box::new(predicate));
52+
}
53+
3654
pub fn stderr(&mut self, bytes: impl AsRef<[u8]>) {
3755
self.stderr = Some(bytes.as_ref().to_vec());
3856
}
3957

58+
pub fn stderr_fn(&mut self, predicate: impl Fn(Vec<u8>) -> bool + 'static) {
59+
self.stderr_predicate = Some(Box::new(predicate));
60+
}
61+
4062
/// Checks all assertions.
4163
pub fn assert(&self, command: &crate::Command) {
4264
if let Some(true) = self.success {
@@ -56,6 +78,13 @@ impl Assertions {
5678
panic!("unexpected status");
5779
}
5880
}
81+
if let Some(predicate) = &self.status_predicate {
82+
let actual = command.get_status().code().expect("failed to retrieve status code");
83+
if !predicate(actual) {
84+
println!("\nunexpected status code: {}", actual);
85+
panic!("unexpected status code");
86+
}
87+
}
5988
if let Some(expected) = &self.stdout {
6089
let actual = command.get_stdout_raw();
6190
if actual != expected {
@@ -64,6 +93,14 @@ impl Assertions {
6493
panic!("unexpected stdout");
6594
}
6695
}
96+
if let Some(predicate) = &self.stdout_predicate {
97+
let actual = command.get_stdout_raw();
98+
if !predicate(actual.into()) {
99+
println!("\nunexpected stdout: {:?}\n", actual);
100+
println!("\n\nunexpected stdout: {}\n", bytes_to_str(actual));
101+
panic!("unexpected stdout");
102+
}
103+
}
67104
if let Some(expected) = &self.stderr {
68105
let actual = command.get_stderr_raw();
69106
if actual != expected {
@@ -72,5 +109,13 @@ impl Assertions {
72109
panic!("unexpected stderr");
73110
}
74111
}
112+
if let Some(predicate) = &self.stderr_predicate {
113+
let actual = command.get_stderr_raw();
114+
if !predicate(actual.into()) {
115+
println!("\nunexpected stderr: {:?}\n", actual);
116+
println!("\n\nunexpected stderr: {}\n", bytes_to_str(actual));
117+
panic!("unexpected stderr");
118+
}
119+
}
75120
}
76121
}

src/command.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,31 @@ impl Command {
8484
self
8585
}
8686

87+
pub fn code_fn(mut self, predicate: impl Fn(i32) -> bool + 'static) -> Self {
88+
self.assertions.code_fn(predicate);
89+
self
90+
}
91+
8792
pub fn stdout(mut self, bytes: impl AsRef<[u8]>) -> Self {
8893
self.assertions.stdout(bytes);
8994
self
9095
}
9196

97+
pub fn stdout_fn(mut self, predicate: impl Fn(Vec<u8>) -> bool + 'static) -> Self {
98+
self.assertions.stdout_fn(predicate);
99+
self
100+
}
101+
92102
pub fn stderr(mut self, bytes: impl AsRef<[u8]>) -> Self {
93103
self.assertions.stderr(bytes);
94104
self
95105
}
96106

107+
pub fn stderr_fn(mut self, predicate: impl Fn(Vec<u8>) -> bool + 'static) -> Self {
108+
self.assertions.stderr_fn(predicate);
109+
self
110+
}
111+
97112
pub fn spawn(&mut self) {
98113
if self.child.is_some() {
99114
panic!("command is already spawned");

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ mod assertions;
22
mod command;
33
mod files;
44
mod macros;
5+
mod predicates;
56
mod utils;
67

78
pub use command::Command;
89
pub use files::TmpFile;
10+
pub use predicates::{and, contains, eq, ge, gt, le, lt, ne, not, or};
911
pub use utils::{sleep, PathExt};

src/predicates.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
pub fn eq<T>(expected: T) -> impl Fn(T) -> bool
2+
where
3+
T: PartialEq,
4+
{
5+
move |actual: T| expected == actual
6+
}
7+
8+
pub fn ne<T>(expected: T) -> impl Fn(T) -> bool
9+
where
10+
T: PartialEq,
11+
{
12+
move |actual: T| expected != actual
13+
}
14+
15+
pub fn ge<T>(expected: T) -> impl Fn(T) -> bool
16+
where
17+
T: PartialOrd,
18+
{
19+
move |actual: T| actual >= expected
20+
}
21+
22+
pub fn gt<T>(expected: T) -> impl Fn(T) -> bool
23+
where
24+
T: PartialOrd,
25+
{
26+
move |actual: T| actual > expected
27+
}
28+
29+
pub fn le<T>(expected: T) -> impl Fn(T) -> bool
30+
where
31+
T: PartialOrd,
32+
{
33+
move |actual: T| actual <= expected
34+
}
35+
36+
pub fn lt<T>(expected: T) -> impl Fn(T) -> bool
37+
where
38+
T: PartialOrd,
39+
{
40+
move |actual: T| actual < expected
41+
}
42+
43+
pub fn and<T>(a: impl Fn(T) -> bool, b: impl Fn(T) -> bool) -> impl Fn(T) -> bool
44+
where
45+
T: Clone,
46+
{
47+
move |actual: T| a(actual.clone()) && b(actual)
48+
}
49+
50+
pub fn or<T>(a: impl Fn(T) -> bool, b: impl Fn(T) -> bool) -> impl Fn(T) -> bool
51+
where
52+
T: Clone,
53+
{
54+
move |actual: T| a(actual.clone()) || b(actual)
55+
}
56+
57+
pub fn not<T>(a: impl Fn(T) -> bool) -> impl Fn(T) -> bool
58+
where
59+
T: Sized,
60+
{
61+
move |actual: T| !a(actual)
62+
}
63+
64+
pub fn contains<T>(needle: T) -> impl Fn(T) -> bool
65+
where
66+
T: AsRef<[u8]>,
67+
{
68+
let needle = String::from_utf8_lossy(needle.as_ref()).to_string();
69+
move |actual: T| String::from_utf8_lossy(actual.as_ref()).contains(&needle)
70+
}

0 commit comments

Comments
 (0)