mapped_command/
return_settings.rs

1use std::{io, string::FromUtf8Error};
2
3use super::OutputMapping;
4use crate::{ExitStatus, UnexpectedExitStatus};
5use thiserror::Error;
6
7/// Error used by various [`OutputMapping`] implementations.
8#[derive(Debug, Error)]
9pub enum CommandExecutionError {
10    /// An io::Error happened, most likely because spawning failed.
11    #[error(transparent)]
12    Io(#[from] io::Error),
13    /// An unexpected exit status appeared.
14    #[error(transparent)]
15    UnexpectedExitStatus(#[from] UnexpectedExitStatus),
16}
17
18/// Return `()` if the program successfully exits.
19#[derive(Debug)]
20pub struct ReturnNothing;
21
22impl OutputMapping for ReturnNothing {
23    type Output = ();
24    type Error = CommandExecutionError;
25
26    fn capture_stdout(&self) -> bool {
27        false
28    }
29
30    fn capture_stderr(&self) -> bool {
31        false
32    }
33
34    fn map_output(
35        self: Box<Self>,
36        _stdout: Option<Vec<u8>>,
37        _stderr: Option<Vec<u8>>,
38        _exit_status: ExitStatus,
39    ) -> Result<Self::Output, Self::Error> {
40        Ok(())
41    }
42}
43
44/// Returns a `Vec<u8>` of the captured stdout if the process exits successfully.
45#[derive(Debug)]
46pub struct ReturnStdout;
47
48impl OutputMapping for ReturnStdout {
49    type Output = Vec<u8>;
50    type Error = CommandExecutionError;
51
52    fn capture_stdout(&self) -> bool {
53        true
54    }
55
56    fn capture_stderr(&self) -> bool {
57        false
58    }
59
60    fn map_output(
61        self: Box<Self>,
62        stdout: Option<Vec<u8>>,
63        _stderr: Option<Vec<u8>>,
64        _exit_status: ExitStatus,
65    ) -> Result<Self::Output, Self::Error> {
66        Ok(stdout.unwrap())
67    }
68}
69
70/// Returns a `Vec<u8>` of the captured stderr if the process exits successfully.
71#[derive(Debug)]
72pub struct ReturnStderr;
73
74impl OutputMapping for ReturnStderr {
75    type Output = Vec<u8>;
76    type Error = CommandExecutionError;
77
78    fn capture_stdout(&self) -> bool {
79        false
80    }
81
82    fn capture_stderr(&self) -> bool {
83        true
84    }
85
86    fn map_output(
87        self: Box<Self>,
88        _stdout: Option<Vec<u8>>,
89        stderr: Option<Vec<u8>>,
90        _exit_status: ExitStatus,
91    ) -> Result<Self::Output, Self::Error> {
92        Ok(stderr.unwrap())
93    }
94}
95
96/// Returns a `Vec<u8>` of the captured stderr and stdout if the process exits successfully.
97#[derive(Debug)]
98pub struct ReturnStdoutAndErr;
99
100impl OutputMapping for ReturnStdoutAndErr {
101    type Output = CapturedStdoutAndErr;
102    type Error = CommandExecutionError;
103
104    fn capture_stdout(&self) -> bool {
105        true
106    }
107
108    fn capture_stderr(&self) -> bool {
109        true
110    }
111
112    fn map_output(
113        self: Box<Self>,
114        stdout: Option<Vec<u8>>,
115        stderr: Option<Vec<u8>>,
116        _exit_status: ExitStatus,
117    ) -> Result<Self::Output, Self::Error> {
118        Ok(CapturedStdoutAndErr {
119            stdout: stdout.unwrap(),
120            stderr: stderr.unwrap(),
121        })
122    }
123}
124
125/// The captured stdout and stderr.
126#[derive(Debug)]
127pub struct CapturedStdoutAndErr {
128    pub stdout: Vec<u8>,
129    pub stderr: Vec<u8>,
130}
131
132/// Maps the captured stdout with given function if the process exited successfully.
133#[derive(Debug)]
134pub struct MapStdout<O, E, F>(pub F)
135where
136    F: FnMut(Vec<u8>) -> Result<O, E> + 'static,
137    E: From<CommandExecutionError> + 'static,
138    O: 'static;
139
140impl<O, E, F> OutputMapping for MapStdout<O, E, F>
141where
142    F: FnMut(Vec<u8>) -> Result<O, E>,
143    E: From<CommandExecutionError>,
144{
145    type Output = O;
146    type Error = E;
147
148    fn capture_stdout(&self) -> bool {
149        true
150    }
151
152    fn capture_stderr(&self) -> bool {
153        false
154    }
155
156    fn map_output(
157        mut self: Box<Self>,
158        stdout: Option<Vec<u8>>,
159        _stderr: Option<Vec<u8>>,
160        _exit_status: ExitStatus,
161    ) -> Result<Self::Output, Self::Error> {
162        (self.0)(stdout.unwrap())
163    }
164}
165
166/// Maps the captured stderr with given function if the process exited successfully.
167#[derive(Debug)]
168pub struct MapStderr<O, E, F>(pub F)
169where
170    F: FnMut(Vec<u8>) -> Result<O, E> + 'static,
171    E: From<CommandExecutionError> + 'static,
172    O: 'static;
173
174impl<O, E, F> OutputMapping for MapStderr<O, E, F>
175where
176    F: FnMut(Vec<u8>) -> Result<O, E>,
177    E: From<CommandExecutionError>,
178{
179    type Output = O;
180    type Error = E;
181
182    fn capture_stdout(&self) -> bool {
183        false
184    }
185
186    fn capture_stderr(&self) -> bool {
187        true
188    }
189
190    fn map_output(
191        mut self: Box<Self>,
192        _stdout: Option<Vec<u8>>,
193        stderr: Option<Vec<u8>>,
194        _exit_status: ExitStatus,
195    ) -> Result<Self::Output, Self::Error> {
196        (self.0)(stderr.unwrap())
197    }
198}
199
200/// Maps the captured stdout and stderr with given function if the process exited successfully.
201#[derive(Debug)]
202pub struct MapStdoutAndErr<O, E, F>(pub F)
203where
204    F: FnMut(CapturedStdoutAndErr) -> Result<O, E> + 'static,
205    E: From<CommandExecutionError> + 'static,
206    O: 'static;
207
208impl<O, E, F> OutputMapping for MapStdoutAndErr<O, E, F>
209where
210    F: FnMut(CapturedStdoutAndErr) -> Result<O, E>,
211    E: From<CommandExecutionError>,
212{
213    type Output = O;
214    type Error = E;
215
216    fn capture_stdout(&self) -> bool {
217        true
218    }
219
220    fn capture_stderr(&self) -> bool {
221        true
222    }
223
224    fn map_output(
225        mut self: Box<Self>,
226        stdout: Option<Vec<u8>>,
227        stderr: Option<Vec<u8>>,
228        _exit_status: ExitStatus,
229    ) -> Result<Self::Output, Self::Error> {
230        (self.0)(CapturedStdoutAndErr {
231            stdout: stdout.unwrap(),
232            stderr: stderr.unwrap(),
233        })
234    }
235}
236
237/// Error from running a command which maps (some) outputs to strings.
238#[derive(Debug, Error)]
239pub enum CommandExecutionWithStringOutputError {
240    /// Spawning failed or bad exit code.
241    #[error(transparent)]
242    Io(#[from] io::Error),
243
244    /// Run into an unexpected exit status.
245    #[error(transparent)]
246    UnexpectedExitStatus(#[from] UnexpectedExitStatus),
247
248    /// Utf8 validation failed.
249    #[error(transparent)]
250    Utf8Error(#[from] FromUtf8Error),
251}
252
253/// Map a stdout/err output (Vec<u8>) to an string.
254///
255/// This is a thin wrapper around [`String::from_utf8()`] which
256/// maps the error to `CommandExecutionWithStringOutputError`.
257fn output_to_string(output: Vec<u8>) -> Result<String, CommandExecutionWithStringOutputError> {
258    Ok(String::from_utf8(output)?)
259}
260
261/// Returns the captured stdout as string, if the process succeeds.
262#[derive(Debug)]
263pub struct ReturnStdoutString;
264
265impl OutputMapping for ReturnStdoutString {
266    type Output = String;
267    type Error = CommandExecutionWithStringOutputError;
268
269    fn capture_stdout(&self) -> bool {
270        true
271    }
272
273    fn capture_stderr(&self) -> bool {
274        false
275    }
276
277    fn map_output(
278        self: Box<Self>,
279        stdout: Option<Vec<u8>>,
280        _stderr: Option<Vec<u8>>,
281        _exit_status: ExitStatus,
282    ) -> Result<Self::Output, Self::Error> {
283        Ok(output_to_string(stdout.unwrap())?)
284    }
285}
286
287/// Returns the captured stderr as string, if the process succeeds.
288#[derive(Debug)]
289pub struct ReturnStderrString;
290
291impl OutputMapping for ReturnStderrString {
292    type Output = String;
293    type Error = CommandExecutionWithStringOutputError;
294
295    fn capture_stdout(&self) -> bool {
296        false
297    }
298
299    fn capture_stderr(&self) -> bool {
300        true
301    }
302
303    fn map_output(
304        self: Box<Self>,
305        _stdout: Option<Vec<u8>>,
306        stderr: Option<Vec<u8>>,
307        _exit_status: ExitStatus,
308    ) -> Result<Self::Output, Self::Error> {
309        Ok(output_to_string(stderr.unwrap())?)
310    }
311}
312
313/// Returns the captured stdout and stderr as strings, if the process succeeds.
314#[derive(Debug)]
315pub struct ReturnStdoutAndErrStrings;
316
317impl OutputMapping for ReturnStdoutAndErrStrings {
318    type Output = CapturedStdoutAndErrStrings;
319    type Error = CommandExecutionWithStringOutputError;
320
321    fn capture_stdout(&self) -> bool {
322        true
323    }
324
325    fn capture_stderr(&self) -> bool {
326        true
327    }
328
329    fn map_output(
330        self: Box<Self>,
331        stdout: Option<Vec<u8>>,
332        stderr: Option<Vec<u8>>,
333        _exit_status: ExitStatus,
334    ) -> Result<Self::Output, Self::Error> {
335        Ok(CapturedStdoutAndErrStrings {
336            stdout: output_to_string(stdout.unwrap())?,
337            stderr: output_to_string(stderr.unwrap())?,
338        })
339    }
340}
341
342/// Capturing of stdout/err converted from bytes to strings.
343#[derive(Debug)]
344pub struct CapturedStdoutAndErrStrings {
345    pub stdout: String,
346    pub stderr: String,
347}
348
349/// Like [`MapStdout`] but converts the captured stdout to an string before mapping.
350#[derive(Debug)]
351pub struct MapStdoutString<O, E, F>(pub F)
352where
353    F: FnMut(String) -> Result<O, E> + 'static,
354    E: From<CommandExecutionWithStringOutputError> + 'static,
355    O: 'static;
356
357impl<O, E, F> OutputMapping for MapStdoutString<O, E, F>
358where
359    F: FnMut(String) -> Result<O, E>,
360    E: From<CommandExecutionWithStringOutputError>,
361{
362    type Output = O;
363    type Error = E;
364
365    fn capture_stdout(&self) -> bool {
366        true
367    }
368
369    fn capture_stderr(&self) -> bool {
370        false
371    }
372
373    fn map_output(
374        mut self: Box<Self>,
375        stdout: Option<Vec<u8>>,
376        _stderr: Option<Vec<u8>>,
377        _exit_status: ExitStatus,
378    ) -> Result<Self::Output, Self::Error> {
379        (self.0)(output_to_string(stdout.unwrap())?)
380    }
381}
382
383/// Like [`MapStderr`] but converts the captured stdout to an string before mapping.
384#[derive(Debug)]
385pub struct MapStderrString<O, E, F>(pub F)
386where
387    F: FnMut(String) -> Result<O, E> + 'static,
388    E: From<CommandExecutionWithStringOutputError> + 'static,
389    O: 'static;
390
391impl<O, E, F> OutputMapping for MapStderrString<O, E, F>
392where
393    F: FnMut(String) -> Result<O, E>,
394    E: From<CommandExecutionWithStringOutputError>,
395{
396    type Output = O;
397    type Error = E;
398
399    fn capture_stdout(&self) -> bool {
400        false
401    }
402
403    fn capture_stderr(&self) -> bool {
404        true
405    }
406
407    fn map_output(
408        mut self: Box<Self>,
409        _stdout: Option<Vec<u8>>,
410        stderr: Option<Vec<u8>>,
411        _exit_status: ExitStatus,
412    ) -> Result<Self::Output, Self::Error> {
413        (self.0)(output_to_string(stderr.unwrap())?)
414    }
415}
416
417/// Like [`MapStdoutAndErr`] but converts the captured stdout and stderr to strings before mapping.
418#[derive(Debug)]
419pub struct MapStdoutAndErrStrings<O, E, F>(pub F)
420where
421    F: FnMut(CapturedStdoutAndErrStrings) -> Result<O, E> + 'static,
422    E: From<CommandExecutionWithStringOutputError> + 'static,
423    O: 'static;
424
425impl<O, E, F> OutputMapping for MapStdoutAndErrStrings<O, E, F>
426where
427    F: FnMut(CapturedStdoutAndErrStrings) -> Result<O, E>,
428    E: From<CommandExecutionWithStringOutputError>,
429{
430    type Output = O;
431    type Error = E;
432
433    fn capture_stdout(&self) -> bool {
434        true
435    }
436
437    fn capture_stderr(&self) -> bool {
438        true
439    }
440
441    fn map_output(
442        mut self: Box<Self>,
443        stdout: Option<Vec<u8>>,
444        stderr: Option<Vec<u8>>,
445        _exit_status: ExitStatus,
446    ) -> Result<Self::Output, Self::Error> {
447        (self.0)(CapturedStdoutAndErrStrings {
448            stdout: output_to_string(stdout.unwrap())?,
449            stderr: output_to_string(stderr.unwrap())?,
450        })
451    }
452}
453
454#[cfg(test)]
455mod tests {
456    #![allow(non_snake_case)]
457
458    use super::{output_to_string, CommandExecutionWithStringOutputError};
459
460    mod ReturnNothing {
461        use super::super::*;
462        use crate::{Command, ExecResult};
463
464        #[test]
465        fn captures_stdout_returns_true() {
466            assert_eq!(ReturnNothing.capture_stdout(), false);
467        }
468
469        #[test]
470        fn captures_stderr_returns_false() {
471            assert_eq!(ReturnNothing.capture_stderr(), false);
472        }
473
474        #[test]
475        fn returns_nothing() {
476            let _: () = Command::new("foo", ReturnNothing)
477                .with_exec_replacement_callback(move |_, _| {
478                    Ok(ExecResult {
479                        exit_status: 0.into(),
480                        stdout: None,
481                        stderr: None,
482                    })
483                })
484                .run()
485                .unwrap();
486        }
487    }
488
489    mod ReturnStdout {
490        use super::super::*;
491        use crate::{Command, ExecResult};
492        use proptest::prelude::*;
493
494        #[test]
495        fn captures_stdout_returns_true() {
496            assert_eq!(ReturnStdout.capture_stdout(), true);
497        }
498
499        #[test]
500        fn captures_stderr_returns_false() {
501            assert_eq!(ReturnStdout.capture_stderr(), false);
502        }
503
504        proptest! {
505            #[test]
506            fn returns_only_captured_std_out_but_not_err(
507                stdout in any::<Vec<u8>>(),
508            ) {
509                let stdout_ = stdout.clone();
510                let out = Command::new("foo", ReturnStdout)
511                    .with_exec_replacement_callback(move |_,_| {
512                        Ok(ExecResult {
513                            exit_status: 0.into(),
514                            stdout: Some(stdout_),
515                            stderr: None
516                        })
517                    })
518                    .run()
519                    .unwrap();
520
521                assert_eq!(out, stdout);
522            }
523        }
524    }
525
526    mod ReturnStderr {
527        use super::super::*;
528        use crate::{Command, ExecResult};
529        use proptest::prelude::*;
530
531        #[test]
532        fn captures_stdout_returns_true() {
533            assert_eq!(ReturnStderr.capture_stdout(), false);
534        }
535
536        #[test]
537        fn captures_stderr_returns_false() {
538            assert_eq!(ReturnStderr.capture_stderr(), true);
539        }
540
541        proptest! {
542            #[test]
543            fn returns_only_captured_std_err_but_not_out(
544                stderr in any::<Vec<u8>>()
545            ) {
546                let stderr_ = stderr.clone();
547                let out = Command::new("foo", ReturnStderr)
548                    .with_exec_replacement_callback(move |_,_| {
549                        Ok(ExecResult {
550                            exit_status: 0.into(),
551                            stdout: None,
552                            stderr: Some(stderr_)
553                        })
554                    })
555                    .run()
556                    .unwrap();
557
558                assert_eq!(out, stderr);
559            }
560        }
561    }
562
563    mod ReturnStdoutAndErr {
564        use super::super::*;
565        use crate::{Command, ExecResult};
566        use proptest::prelude::*;
567
568        #[test]
569        fn captures_stdout_returns_true() {
570            assert_eq!(ReturnStdoutAndErr.capture_stdout(), true);
571        }
572
573        #[test]
574        fn captures_stderr_returns_false() {
575            assert_eq!(ReturnStdoutAndErr.capture_stderr(), true);
576        }
577
578        proptest! {
579            #[test]
580            fn returns_captured_std_out_and_err(
581                stdout in any::<Vec<u8>>(),
582                stderr in any::<Vec<u8>>()
583            ) {
584                let stdout_ = stdout.clone();
585                let stderr_ = stderr.clone();
586                let out: CapturedStdoutAndErr = Command::new("foo", ReturnStdoutAndErr)
587                    .with_exec_replacement_callback(move |_,_| {
588                        Ok(ExecResult {
589                            exit_status: 0.into(),
590                            stdout: Some(stdout_),
591                            stderr: Some(stderr_)
592                        })
593                    })
594                    .run()
595                    .unwrap();
596
597                assert_eq!(out.stdout, stdout);
598                assert_eq!(out.stderr, stderr);
599            }
600        }
601    }
602
603    mod MapStdout {
604        use super::super::*;
605        use crate::{Command, ExecResult};
606
607        #[test]
608        fn maps_stdout_to_a_result() {
609            let res = Command::new(
610                "foo",
611                MapStdout(|out| -> Result<u32, Box<dyn std::error::Error>> {
612                    Ok(String::from_utf8(out)?.parse()?)
613                }),
614            )
615            .with_exec_replacement_callback(|_, _| {
616                Ok(ExecResult {
617                    exit_status: 0.into(),
618                    stdout: Some("3241".into()),
619                    stderr: None,
620                })
621            })
622            .run()
623            .unwrap();
624
625            assert_eq!(res, 3241u32);
626        }
627
628        #[test]
629        fn mapping_stdout_to_a_result_can_fail() {
630            Command::new(
631                "foo",
632                MapStdout(|out| -> Result<u32, Box<dyn std::error::Error>> {
633                    Ok(String::from_utf8(out)?.parse()?)
634                }),
635            )
636            .with_exec_replacement_callback(|_, _| {
637                Ok(ExecResult {
638                    exit_status: 0.into(),
639                    stdout: Some("abcd".into()),
640                    stderr: None,
641                })
642            })
643            .run()
644            .unwrap_err();
645        }
646    }
647
648    mod MapStderr {
649        use super::super::*;
650        use crate::{Command, ExecResult};
651
652        #[test]
653        fn maps_stderr_to_a_result() {
654            let res = Command::new(
655                "foo",
656                MapStderr(|err| -> Result<u32, Box<dyn std::error::Error>> {
657                    Ok(String::from_utf8(err)?.parse()?)
658                }),
659            )
660            .with_exec_replacement_callback(|_, _| {
661                Ok(ExecResult {
662                    exit_status: 0.into(),
663                    stderr: Some("3241".into()),
664                    stdout: None,
665                })
666            })
667            .run()
668            .unwrap();
669
670            assert_eq!(res, 3241u32);
671        }
672
673        #[test]
674        fn mapping_stderr_to_a_result_can_fail() {
675            Command::new(
676                "foo",
677                MapStderr(|err| -> Result<u32, Box<dyn std::error::Error>> {
678                    Ok(String::from_utf8(err)?.parse()?)
679                }),
680            )
681            .with_exec_replacement_callback(|_, _| {
682                Ok(ExecResult {
683                    exit_status: 0.into(),
684                    stdout: None,
685                    stderr: Some("abcd".into()),
686                })
687            })
688            .run()
689            .unwrap_err();
690        }
691    }
692
693    mod MapStdoutAndErr {
694        use super::super::*;
695        use crate::{Command, ExecResult};
696
697        #[test]
698        fn maps_stdout_to_a_result() {
699            let res = Command::new(
700                "foo",
701                MapStdoutAndErr(|cap| -> Result<(u32, u32), Box<dyn std::error::Error>> {
702                    let out_res = String::from_utf8(cap.stdout)?.parse()?;
703                    let err_res = String::from_utf8(cap.stderr)?.parse()?;
704                    Ok((out_res, err_res))
705                }),
706            )
707            .with_exec_replacement_callback(|_, _| {
708                Ok(ExecResult {
709                    exit_status: 0.into(),
710                    stdout: Some("3241".into()),
711                    stderr: Some("1242".into()),
712                })
713            })
714            .run()
715            .unwrap();
716
717            assert_eq!(res, (3241u32, 1242u32));
718        }
719
720        #[test]
721        fn mapping_stdout_to_a_result_can_fail() {
722            Command::new(
723                "foo",
724                MapStdoutAndErr(|_| -> Result<u32, Box<dyn std::error::Error>> {
725                    Err("yes this fails")?
726                }),
727            )
728            .with_exec_replacement_callback(|_, _| {
729                Ok(ExecResult {
730                    exit_status: 0.into(),
731                    stdout: Some(Vec::new()),
732                    stderr: Some(Vec::new()),
733                })
734            })
735            .run()
736            .unwrap_err();
737        }
738    }
739
740    fn is_utf8_error(err: &CommandExecutionWithStringOutputError) -> bool {
741        if let CommandExecutionWithStringOutputError::Utf8Error(_) = err {
742            true
743        } else {
744            false
745        }
746    }
747
748    #[test]
749    fn output_to_string_fails_on_bad_strings() {
750        let err = output_to_string(vec![0xFF, 0xFF, 0xFF]).unwrap_err();
751
752        if !is_utf8_error(&err) {
753            panic!("unexpected error: {:?}", err);
754        }
755    }
756
757    #[test]
758    fn output_to_string_converts_utf8_bytes_to_string() {
759        let out = output_to_string("hy".to_owned().into_bytes()).unwrap();
760        assert_eq!(out, "hy");
761    }
762
763    mod ReturnStdoutString {
764        use super::super::*;
765        use crate::{Command, ExecResult};
766        use proptest::prelude::*;
767        use tests::is_utf8_error;
768
769        #[test]
770        fn captures_stdout_returns_true() {
771            assert_eq!(ReturnStdoutString.capture_stdout(), true);
772        }
773
774        #[test]
775        fn captures_stderr_returns_false() {
776            assert_eq!(ReturnStdoutString.capture_stderr(), false);
777        }
778
779        proptest! {
780            #[test]
781            fn returns_only_captured_std_out_but_not_err(
782                stdout in any::<Vec<u8>>(),
783            ) {
784                let stdout_ = stdout.clone();
785                let res = Command::new("foo", ReturnStdoutString)
786                    .with_exec_replacement_callback(move |_,_| {
787                        Ok(ExecResult {
788                            exit_status: 0.into(),
789                            stdout: Some(stdout_),
790                            stderr: None
791                        })
792                    })
793                    .run();
794
795                let expected = output_to_string(stdout);
796
797                match expected {
798                    Ok(expected) => {
799                        let got = res.unwrap();
800                        assert_eq!(expected, got);
801                    },
802                    Err(error) => {
803                        assert!(is_utf8_error(&error));
804                    }
805                }
806            }
807        }
808    }
809
810    mod ReturnStderrString {
811        use super::{super::*, is_utf8_error};
812        use crate::{Command, ExecResult};
813        use proptest::prelude::*;
814
815        #[test]
816        fn captures_stdout_returns_true() {
817            assert_eq!(ReturnStderrString.capture_stdout(), false);
818        }
819
820        #[test]
821        fn captures_stderr_returns_false() {
822            assert_eq!(ReturnStderrString.capture_stderr(), true);
823        }
824
825        proptest! {
826            #[test]
827            fn returns_only_captured_std_err_but_not_out(
828                stderr in any::<Vec<u8>>()
829            ) {
830                let stderr_ = stderr.clone();
831                let res = Command::new("foo", ReturnStderrString)
832                    .with_exec_replacement_callback(move |_,_| {
833                        Ok(ExecResult {
834                            exit_status: 0.into(),
835                            stdout: None,
836                            stderr: Some(stderr_)
837                        })
838                    })
839                    .run();
840
841                let expected = output_to_string(stderr);
842
843                match expected {
844                    Ok(expected) => {
845                        let got = res.unwrap();
846                        assert_eq!(expected, got);
847                    },
848                    Err(error) => {
849                        assert!(is_utf8_error(&error));
850                    }
851                }
852            }
853        }
854    }
855
856    mod ReturnStdoutAndErrStrings {
857        use super::{super::*, is_utf8_error};
858        use crate::{Command, ExecResult};
859        use proptest::prelude::*;
860
861        #[test]
862        fn captures_stdout_returns_true() {
863            assert_eq!(ReturnStdoutAndErrStrings.capture_stdout(), true);
864        }
865
866        #[test]
867        fn captures_stderr_returns_false() {
868            assert_eq!(ReturnStdoutAndErrStrings.capture_stderr(), true);
869        }
870
871        proptest! {
872            #[test]
873            fn returns_captured_std_out_and_err(
874                stdout in any::<Vec<u8>>(),
875                stderr in any::<Vec<u8>>()
876            ) {
877                let stdout_ = stdout.clone();
878                let stderr_ = stderr.clone();
879                let res = Command::new("foo", ReturnStdoutAndErrStrings)
880                    .with_exec_replacement_callback(move |_,_| {
881                        Ok(ExecResult {
882                            exit_status: 0.into(),
883                            stdout: Some(stdout_),
884                            stderr: Some(stderr_)
885                        })
886                    })
887                    .run();
888
889                let expected = output_to_string(stdout)
890                    .and_then(|stdout| Ok((stdout, output_to_string(stderr)?)));
891
892                match expected {
893                    Ok((expected_stdout, expected_stderr)) => {
894                        let got = res.unwrap();
895                        assert_eq!(expected_stdout, got.stdout);
896                        assert_eq!(expected_stderr, got.stderr);
897                    },
898                    Err(error) => {
899                        assert!(is_utf8_error(&error));
900                    }
901                }
902            }
903        }
904    }
905
906    mod MapStdoutStrings {
907        use super::super::*;
908        use crate::{Command, ExecResult};
909
910        #[test]
911        fn maps_stdout_to_a_result() {
912            let res = Command::new(
913                "foo",
914                MapStdoutString(|out| -> Result<u32, Box<dyn std::error::Error>> {
915                    Ok(out.parse()?)
916                }),
917            )
918            .with_exec_replacement_callback(|_, _| {
919                Ok(ExecResult {
920                    exit_status: 0.into(),
921                    stdout: Some("3241".into()),
922                    stderr: None,
923                })
924            })
925            .run()
926            .unwrap();
927
928            assert_eq!(res, 3241u32);
929        }
930
931        #[test]
932        fn mapping_stdout_to_a_result_can_fail() {
933            Command::new(
934                "foo",
935                MapStdoutString(|out| -> Result<u32, Box<dyn std::error::Error>> {
936                    Ok(out.parse()?)
937                }),
938            )
939            .with_exec_replacement_callback(|_, _| {
940                Ok(ExecResult {
941                    exit_status: 0.into(),
942                    stdout: Some("abcd".into()),
943                    stderr: None,
944                })
945            })
946            .run()
947            .unwrap_err();
948        }
949    }
950
951    mod MapStderrString {
952        use super::super::*;
953        use crate::{Command, ExecResult};
954
955        #[test]
956        fn maps_stderr_to_a_result() {
957            let res = Command::new(
958                "foo",
959                MapStderrString(|err| -> Result<u32, Box<dyn std::error::Error>> {
960                    Ok(err.parse()?)
961                }),
962            )
963            .with_exec_replacement_callback(|_, _| {
964                Ok(ExecResult {
965                    exit_status: 0.into(),
966                    stderr: Some("3241".into()),
967                    stdout: None,
968                })
969            })
970            .run()
971            .unwrap();
972
973            assert_eq!(res, 3241u32);
974        }
975
976        #[test]
977        fn mapping_stderr_to_a_result_can_fail() {
978            Command::new(
979                "foo",
980                MapStderrString(|err| -> Result<u32, Box<dyn std::error::Error>> {
981                    Ok(err.parse()?)
982                }),
983            )
984            .with_exec_replacement_callback(|_, _| {
985                Ok(ExecResult {
986                    exit_status: 0.into(),
987                    stdout: None,
988                    stderr: Some("abcd".into()),
989                })
990            })
991            .run()
992            .unwrap_err();
993        }
994    }
995
996    mod MapStdoutAndErrStrings {
997        use super::super::*;
998        use crate::{Command, ExecResult};
999
1000        #[test]
1001        fn maps_stdout_to_a_result() {
1002            let res = Command::new(
1003                "foo",
1004                MapStdoutAndErrStrings(|cap| -> Result<(u32, u32), Box<dyn std::error::Error>> {
1005                    let out_res = cap.stdout.parse()?;
1006                    let err_res = cap.stderr.parse()?;
1007                    Ok((out_res, err_res))
1008                }),
1009            )
1010            .with_exec_replacement_callback(|_, _| {
1011                Ok(ExecResult {
1012                    exit_status: 0.into(),
1013                    stdout: Some("3241".into()),
1014                    stderr: Some("1242".into()),
1015                })
1016            })
1017            .run()
1018            .unwrap();
1019
1020            assert_eq!(res, (3241u32, 1242u32));
1021        }
1022
1023        #[test]
1024        fn mapping_stdout_to_a_result_can_fail() {
1025            Command::new(
1026                "foo",
1027                MapStdoutAndErrStrings(|_| -> Result<u32, Box<dyn std::error::Error>> {
1028                    Err("yes this fails")?
1029                }),
1030            )
1031            .with_exec_replacement_callback(|_, _| {
1032                Ok(ExecResult {
1033                    exit_status: 0.into(),
1034                    stdout: Some(Vec::new()),
1035                    stderr: Some(Vec::new()),
1036                })
1037            })
1038            .run()
1039            .unwrap_err();
1040        }
1041    }
1042
1043    //TODO proptest against string parsing failure in Map* OutputMapping
1044}