1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
/// Internal namespace.
mod private
{
use std::borrow::Cow;
/// Captured output from a completed script execution.
///
/// Holds the exit status code, complete standard output, and complete standard
/// error as raw byte sequences. Assertion methods on this type are designed for
/// use in Rust test functions — they panic with a descriptive message on failure
/// rather than returning a `Result`.
#[ derive( Debug, Clone, Default ) ]
pub struct CapturedOutput
{
/// Exit code of the child process. Zero indicates success.
pub exit_status : i32,
/// Complete standard output captured from the process.
pub stdout : Vec< u8 >,
/// Complete standard error captured from the process.
pub stderr : Vec< u8 >,
}
impl CapturedOutput
{
/// Decode stdout as UTF-8, lossily replacing invalid byte sequences.
#[ must_use ]
pub fn stdout_str( &self ) -> Cow< '_, str >
{
String::from_utf8_lossy( &self.stdout )
}
/// Decode stderr as UTF-8, lossily replacing invalid byte sequences.
#[ must_use ]
pub fn stderr_str( &self ) -> Cow< '_, str >
{
String::from_utf8_lossy( &self.stderr )
}
// ── Predicates ──────────────────────────────────────────────────────────────
/// Returns `true` if exit status is zero.
#[ must_use ]
pub fn exit_ok( &self ) -> bool
{
self.exit_status == 0
}
/// Returns `true` if stdout exactly equals `expected` after UTF-8 decoding.
#[ must_use ]
pub fn stdout_eq( &self, expected : &str ) -> bool
{
self.stdout_str() == expected
}
/// Returns `true` if stderr exactly equals `expected` after UTF-8 decoding.
#[ must_use ]
pub fn stderr_eq( &self, expected : &str ) -> bool
{
self.stderr_str() == expected
}
/// Returns `true` if stdout contains `needle` as a substring.
#[ must_use ]
pub fn stdout_contains( &self, needle : &str ) -> bool
{
self.stdout_str().contains( needle )
}
/// Returns `true` if stderr contains `needle` as a substring.
#[ must_use ]
pub fn stderr_contains( &self, needle : &str ) -> bool
{
self.stderr_str().contains( needle )
}
// ── Assertions (panic on failure) ────────────────────────────────────────────
/// Asserts that exit status is zero.
///
/// # Panics
///
/// Panics with exit code and stderr content when exit status is non-zero.
pub fn assert_exit_ok( &self )
{
let exit_status = self.exit_status;
let stderr = self.stderr_str();
assert!
(
self.exit_ok(),
"expected exit status 0, got {exit_status}\nstderr:\n{stderr}",
);
}
/// Asserts that stdout exactly equals `expected`.
///
/// # Panics
///
/// Panics with expected/actual diff when stdout does not exactly match.
pub fn assert_stdout_eq( &self, expected : &str )
{
let actual = self.stdout_str();
assert!
(
actual == expected,
"stdout mismatch\nexpected : {expected:?}\nactual : {actual:?}",
);
}
/// Asserts that stderr exactly equals `expected`.
///
/// # Panics
///
/// Panics with expected/actual diff when stderr does not exactly match.
pub fn assert_stderr_eq( &self, expected : &str )
{
let actual = self.stderr_str();
assert!
(
actual == expected,
"stderr mismatch\nexpected : {expected:?}\nactual : {actual:?}",
);
}
/// Asserts that stdout contains `needle`.
///
/// # Panics
///
/// Panics showing the needle and full stdout content when not found.
pub fn assert_stdout_contains( &self, needle : &str )
{
let actual = self.stdout_str();
assert!
(
actual.contains( needle ),
"stdout does not contain {needle:?}\nstdout : {actual:?}",
);
}
/// Asserts that stderr contains `needle`.
///
/// # Panics
///
/// Panics showing the needle and full stderr content when not found.
pub fn assert_stderr_contains( &self, needle : &str )
{
let actual = self.stderr_str();
assert!
(
actual.contains( needle ),
"stderr does not contain {needle:?}\nstderr : {actual:?}",
);
}
/// Asserts that stdout is empty (zero bytes).
///
/// # Panics
///
/// Panics showing the actual stdout content when non-empty.
pub fn assert_stdout_empty( &self )
{
let actual = self.stdout_str();
assert!( actual.is_empty(), "expected empty stdout, got : {actual:?}" );
}
/// Asserts that stderr is empty (zero bytes).
///
/// # Panics
///
/// Panics showing the actual stderr content when non-empty.
pub fn assert_stderr_empty( &self )
{
let actual = self.stderr_str();
assert!( actual.is_empty(), "expected empty stderr, got : {actual:?}" );
}
}
}
mod_interface::mod_interface!
{
exposed use private::CapturedOutput;
prelude use private::CapturedOutput;
}