Skip to content

Commit b1376f9

Browse files
committed
fix: Better group API
- `cmd` was split between `cmd` and `errors`. - Kind of uneven what was brought into the top-level. This is less necessary with good with good docs. Fixes #40, I hope BREAKING CHANGE: API organization changed.
1 parent 1e0ece8 commit b1376f9

File tree

5 files changed

+239
-245
lines changed

5 files changed

+239
-245
lines changed

src/assert.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ use predicates::str::PredicateStrExt;
1111
use predicates_core;
1212
use predicates_tree::CaseTreeExt;
1313

14-
use errors::dump_buffer;
15-
use errors::output_fmt;
14+
use cmd::dump_buffer;
15+
use cmd::output_fmt;
1616

1717
/// Assert the state of an [`Output`].
1818
///

src/cmd.rs

+227-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
use std::process;
1+
//! Simplify one-off runs of programs.
22
3-
use errors::dump_buffer;
4-
use errors::OutputError;
5-
use errors::OutputResult;
3+
use std::error::Error;
4+
use std::fmt;
5+
use std::process;
6+
use std::str;
67

78
/// Converts a type to an [`OutputResult`].
89
///
@@ -128,3 +129,225 @@ impl<'c> OutputOkExt for &'c mut process::Command {
128129
}
129130
}
130131
}
132+
133+
/// [`Output`] represented as a [`Result`].
134+
///
135+
/// Generally produced by [`OutputOkExt`].
136+
///
137+
/// # Examples
138+
///
139+
/// ```rust
140+
/// use assert_cmd::prelude::*;
141+
///
142+
/// use std::process::Command;
143+
///
144+
/// let result = Command::new("echo")
145+
/// .args(&["42"])
146+
/// .ok();
147+
/// assert!(result.is_ok());
148+
/// ```
149+
///
150+
/// [`Output`]: https://doc.rust-lang.org/std/process/struct.Output.html
151+
/// [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html
152+
/// [`OutputOkExt`]: trait.OutputOkExt.html
153+
pub type OutputResult = Result<process::Output, OutputError>;
154+
155+
/// [`Command`] error.
156+
///
157+
/// Generally produced by [`OutputOkExt`].
158+
///
159+
/// # Examples
160+
///
161+
/// ```rust
162+
/// use assert_cmd::prelude::*;
163+
///
164+
/// use std::process::Command;
165+
///
166+
/// let err = Command::main_binary()
167+
/// .unwrap()
168+
/// .env("exit", "42")
169+
/// .unwrap_err();
170+
/// ```
171+
///
172+
/// [`Command`]: https://doc.rust-lang.org/std/process/struct.Command.html
173+
/// [`OutputOkExt`]: trait.OutputOkExt.html
174+
#[derive(Debug)]
175+
pub struct OutputError {
176+
cmd: Option<String>,
177+
stdin: Option<Vec<u8>>,
178+
cause: OutputCause,
179+
}
180+
181+
impl OutputError {
182+
/// Convert [`Output`] into an [`Error`].
183+
///
184+
/// [`Output`]: https://doc.rust-lang.org/std/process/struct.Output.html
185+
/// [`Error`]: https://doc.rust-lang.org/std/error/trait.Error.html
186+
pub fn new(output: process::Output) -> Self {
187+
Self {
188+
cmd: None,
189+
stdin: None,
190+
cause: OutputCause::Expected(Output { output }),
191+
}
192+
}
193+
194+
/// For errors that happen in creating a [`Output`].
195+
///
196+
/// [`Output`]: https://doc.rust-lang.org/std/process/struct.Output.html
197+
pub fn with_cause<E>(cause: E) -> Self
198+
where
199+
E: Error + Send + Sync + 'static,
200+
{
201+
Self {
202+
cmd: None,
203+
stdin: None,
204+
cause: OutputCause::Unexpected(Box::new(cause)),
205+
}
206+
}
207+
208+
/// Add the command line for additional context.
209+
pub fn set_cmd(mut self, cmd: String) -> Self {
210+
self.cmd = Some(cmd);
211+
self
212+
}
213+
214+
/// Add the `stdin` for additional context.
215+
pub fn set_stdin(mut self, stdin: Vec<u8>) -> Self {
216+
self.stdin = Some(stdin);
217+
self
218+
}
219+
220+
/// Access the contained [`Output`].
221+
///
222+
/// # Examples
223+
///
224+
/// ```rust
225+
/// use assert_cmd::prelude::*;
226+
///
227+
/// use std::process::Command;
228+
///
229+
/// let err = Command::main_binary()
230+
/// .unwrap()
231+
/// .env("exit", "42")
232+
/// .unwrap_err();
233+
/// let output = err
234+
/// .as_output()
235+
/// .unwrap();
236+
/// assert_eq!(Some(42), output.status.code());
237+
/// ```
238+
///
239+
/// [`Output`]: https://doc.rust-lang.org/std/process/struct.Output.html
240+
pub fn as_output(&self) -> Option<&process::Output> {
241+
match self.cause {
242+
OutputCause::Expected(ref e) => Some(&e.output),
243+
OutputCause::Unexpected(_) => None,
244+
}
245+
}
246+
}
247+
248+
impl Error for OutputError {
249+
fn description(&self) -> &str {
250+
"Command failed."
251+
}
252+
253+
fn cause(&self) -> Option<&Error> {
254+
if let OutputCause::Unexpected(ref err) = self.cause {
255+
Some(err.as_ref())
256+
} else {
257+
None
258+
}
259+
}
260+
}
261+
262+
impl fmt::Display for OutputError {
263+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
264+
if let Some(ref cmd) = self.cmd {
265+
writeln!(f, "command=`{}`", cmd)?;
266+
}
267+
if let Some(ref stdin) = self.stdin {
268+
if let Ok(stdin) = str::from_utf8(stdin) {
269+
writeln!(f, "stdin=```{}```", stdin)?;
270+
} else {
271+
writeln!(f, "stdin=```{:?}```", stdin)?;
272+
}
273+
}
274+
write!(f, "{}", self.cause)
275+
}
276+
}
277+
278+
#[derive(Debug)]
279+
enum OutputCause {
280+
Expected(Output),
281+
Unexpected(Box<Error + Send + Sync + 'static>),
282+
}
283+
284+
impl fmt::Display for OutputCause {
285+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
286+
match *self {
287+
OutputCause::Expected(ref e) => write!(f, "{}", e),
288+
OutputCause::Unexpected(ref e) => write!(f, "{}", e),
289+
}
290+
}
291+
}
292+
293+
#[derive(Debug)]
294+
struct Output {
295+
output: process::Output,
296+
}
297+
298+
impl fmt::Display for Output {
299+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
300+
output_fmt(&self.output, f)
301+
}
302+
}
303+
304+
pub(crate) fn output_fmt(output: &process::Output, f: &mut fmt::Formatter) -> fmt::Result {
305+
if let Some(code) = output.status.code() {
306+
writeln!(f, "code={}", code)?;
307+
} else {
308+
writeln!(f, "code=<interrupted>")?;
309+
}
310+
311+
write!(f, "stdout=```")?;
312+
write_buffer(&output.stdout, f)?;
313+
writeln!(f, "```")?;
314+
315+
write!(f, "stderr=```")?;
316+
write_buffer(&output.stderr, f)?;
317+
writeln!(f, "```")?;
318+
319+
Ok(())
320+
}
321+
322+
pub(crate) fn dump_buffer(buffer: &[u8]) -> String {
323+
if let Ok(buffer) = str::from_utf8(buffer) {
324+
buffer.to_string()
325+
} else {
326+
format!("{:?}", buffer)
327+
}
328+
}
329+
330+
pub(crate) fn write_buffer(buffer: &[u8], f: &mut fmt::Formatter) -> fmt::Result {
331+
if let Ok(buffer) = str::from_utf8(buffer) {
332+
write!(f, "{}", buffer)
333+
} else {
334+
write!(f, "{:?}", buffer)
335+
}
336+
}
337+
338+
#[derive(Debug)]
339+
pub(crate) struct DebugBuffer {
340+
buffer: Vec<u8>,
341+
}
342+
343+
impl DebugBuffer {
344+
pub(crate) fn new(buffer: Vec<u8>) -> Self {
345+
DebugBuffer { buffer }
346+
}
347+
}
348+
349+
impl fmt::Display for DebugBuffer {
350+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
351+
write_buffer(&self.buffer, f)
352+
}
353+
}

0 commit comments

Comments
 (0)