mapped_command/
lib.rs

1//! A `std::process::Command` replacement which is a bit more flexible and testable.
2//!
3//! For now this is focused on cases which wait until the subprocess is completed
4//! and then map the output (or do not care about the output).
5//!
6//! - by default check the exit status
7//!
8//! - bundle a mapping of the captured stdout/stderr to an result into the command,
9//!   i.e. the `Command` type is `Command<Output, Error>` e.g. `Command<Vec<String>, Error>`.
10//!
11//! - implicitly define if stdout/stderr needs to be captured to prevent mistakes
12//!   wrt. this, this is done through through the same mechanism which is used to
13//!   define how the output is mapped, e.g. `Command::new("ls", ReturnStdoutString)`
14//!   will implicitly enabled stdout capturing and disable `stderr` capturing.
15//!
16//! - allow replacing command execution with an callback, this is mainly used to
17//!   allow mocking the command.
18//!
19//! - besides allowing to decide weather the sub-process should inherit the environment and
20//!   which variables get removed/set/overwritten this type also allows you to whitelist which
21//!   env variables should be inherited.
22//!
23//! - do not have `&mut self` pass through based API. This makes it more bothersome to create
24//!   functions which create and return commands, which this types intents to make simple so
25//!   that you can e.g. have a function like `fn ls_command() -> Command<Vec<String>, Error>`
26//!   which returns a command which if run runs the ls command and returns a vector of string
27//!   (or an error if spawning, running or utf8 validation fails).
28//!
29//! - be generic over Output and Error type but dynamic over how the captured stdout/err is
30//!   mapped to the given `Result<Output, Error>`. This allows you to e.g. at runtime switch
31//!   between different function which create a command with the same output but on different
32//!   ways (i.e. with different called programs and output mapping, e.g. based on a config
33//!   setting).
34//!
35//! # Basic Examples
36//!
37//! ```rust
38//! use mapped_command::{Command, MapStdoutString, ReturnStdoutString, ExecResult, CommandExecutionWithStringOutputError as Error};
39//!
40//! /// Usage: `echo().run()`.
41//! fn echo() -> Command<String, Error> {
42//!     // implicitly enables stdout capturing but not stderr capturing
43//!     // and converts the captured bytes to string
44//!     Command::new("echo", ReturnStdoutString)
45//! }
46//!
47//! /// Usage: `ls_command().run()`.
48//! fn ls_command() -> Command<Vec<String>, Error> {
49//!     Command::new("ls", MapStdoutString(|out| {
50//!         let lines = out.lines().map(Into::into).collect::<Vec<_>>();
51//!         Ok(lines)
52//!     }))
53//! }
54//!
55//! fn main() {
56//!     let res = ls_command()
57//!         //mock
58//!         .with_exec_replacement_callback(|_cmd, _rs| {
59//!             Ok(ExecResult {
60//!                 exit_status: 0.into(),
61//!                 // Some indicates in the mock that stdout was captured, None would mean it was not.
62//!                 stdout: Some("foo\nbar\ndoor\n".to_owned().into()),
63//!                 ..Default::default()
64//!             })
65//!         })
66//!         // run, check exit status and map captured outputs
67//!         .run()
68//!         .unwrap();
69//!
70//!     assert_eq!(res, vec!["foo", "bar", "door"]);
71//!
72//!     let err = ls_command()
73//!         //mock
74//!         .with_exec_replacement_callback(|_cmd, _rs| {
75//!             Ok(ExecResult {
76//!                 exit_status: 1.into(),
77//!                 stdout: Some("foo\nbar\ndoor\n".to_owned().into()),
78//!                 ..Default::default()
79//!             })
80//!         })
81//!         .run()
82//!         .unwrap_err();
83//!
84//!     assert_eq!(err.to_string(), "Unexpected exit status. Got: 0x1, Expected: 0x0");
85//! }
86//! ```
87//!
88//! # Handling arguments and environment variables
89//!
90//! ```rust
91//! use mapped_command::{Command,ReturnStdoutString, EnvChange};
92//! # #[cfg(unix)]
93//! # fn main() {
94//! std::env::set_var("FOOBAR", "the foo");
95//! std::env::set_var("DODO", "no no");
96//! let echoed = Command::new("bash", ReturnStdoutString)
97//!     .with_arguments(&["-c", "echo $0 ${DODO:-yo} $FOOBAR $BARFOOT $(pwd)", "arg1"])
98//!     .with_inherit_env(false)
99//!     .with_env_update("BARFOOT", "the bar")
100//!     //inherit this even if env inheritance is disabled (it is see above)
101//!     .with_env_update("FOOBAR", EnvChange::Inherit)
102//!     .with_working_directory_override(Some("/usr"))
103//!     .run()
104//!     .unwrap();
105//!
106//! assert_eq!(echoed, "arg1 yo the foo the bar /usr\n");
107//! # }
108//! ```
109//!
110use std::{
111    borrow::Cow,
112    collections::HashMap,
113    env::{self, VarsOs},
114    ffi::{OsStr, OsString},
115    fmt,
116    fmt::Display,
117    io,
118    path::{Path, PathBuf},
119};
120use thiserror::Error;
121
122pub use self::return_settings::*;
123
124#[macro_use]
125mod utils;
126mod return_settings;
127mod sys;
128
129/// A alternative to `std::process::Command` see module level documentation.
130pub struct Command<Output, Error>
131where
132    Output: 'static,
133    Error: From<io::Error> + From<UnexpectedExitStatus> + 'static,
134{
135    program: OsString,
136    arguments: Vec<OsString>,
137    env_updates: HashMap<OsString, EnvChange>,
138    working_directory_override: Option<PathBuf>,
139    expected_exit_status: ExitStatus,
140    check_exit_status: bool,
141    inherit_env: bool,
142    return_settings: Option<Box<dyn OutputMapping<Output = Output, Error = Error>>>,
143    run_callback: Option<
144        Box<
145            dyn FnOnce(
146                Self,
147                &dyn OutputMapping<Output = Output, Error = Error>,
148            ) -> Result<ExecResult, io::Error>,
149        >,
150    >,
151}
152
153impl<Output, Error> Command<Output, Error>
154where
155    Output: 'static,
156    Error: From<io::Error> + From<UnexpectedExitStatus> + 'static,
157{
158    /// Create a new command for given program and output mapping.
159    ///
160    /// The output mapping will imply if stdout/stderr is captured and how the
161    /// captured output is mapped to a `Result<Self::Output, Self::Error>`.
162    ///
163    pub fn new(
164        program: impl Into<OsString>,
165        return_settings: impl OutputMapping<Output = Output, Error = Error>,
166    ) -> Self {
167        Command {
168            program: program.into(),
169            arguments: Vec::new(),
170            env_updates: HashMap::new(),
171            check_exit_status: true,
172            inherit_env: true,
173            expected_exit_status: ExitStatus::Code(0),
174            return_settings: Some(Box::new(return_settings) as _),
175            working_directory_override: None,
176            run_callback: Some(Box::new(sys::actual_exec_exec_replacement_callback)),
177        }
178    }
179
180    /// Return the program the command will run.
181    pub fn program(&self) -> &OsStr {
182        &*self.program
183    }
184
185    /// Returns the arguments passed the the program when run.
186    pub fn arguments(&self) -> &[OsString] {
187        &self.arguments
188    }
189
190    /// Returns this command with new arguments added to the end of the argument list
191    pub fn with_arguments<T>(mut self, args: impl IntoIterator<Item = T>) -> Self
192    where
193        T: Into<OsString>,
194    {
195        self.arguments.extend(args.into_iter().map(|v| v.into()));
196        self
197    }
198
199    /// Returns this command with a new argument added to the end of the argument list
200    pub fn with_argument(mut self, arg: impl Into<OsString>) -> Self {
201        self.arguments.push(arg.into());
202        self
203    }
204
205    /// Return a map of all env variables which will be set/overwritten in the subprocess.
206    ///
207    /// # Warning
208    ///
209    /// The keys of env variables have not *not* been evaluated for syntactic validity.
210    /// So the given keys can cause process spawning or calls to [`std::env::set_var`] to
211    /// fail.
212    pub fn env_updates(&self) -> &HashMap<OsString, EnvChange> {
213        &self.env_updates
214    }
215
216    /// Returns this command with the map of env updates updated by given iterator of key value pairs.
217    ///
218    /// If any key from the new map already did exist in the current updates it will
219    /// replace the old key & value.
220    ///
221    /// - Common supported values for keys include `OsString`, `&OsStr`, `String`, `&str`.
222    /// - Common supported values for values include `EnvChange`, `OsString`, `&OsStr`, `String`,
223    ///   `&str`
224    ///
225    /// So you can pass in containers like `Vec<(&str, &str)>`, `HashMap<&str, &str>` or
226    /// `HashMap<OsString, EnvChange>`, etc.
227    ///
228    /// # Warning
229    ///
230    /// The keys of env variables will *not* be evaluated for syntactic validity.
231    /// Setting a key invalid on given platform *might* cause the process spawning to
232    /// fail (e.g. using a key lik `"="` or `""`). It also *might* also do other thinks
233    /// like the env variable being passed in but being unaccessible or similar. It's completely
234    /// dependent on the OS and the impl. of `std::process::Command` or whatever is used to
235    /// execute the command.
236    pub fn with_env_updates<K, V>(mut self, map: impl IntoIterator<Item = (K, V)>) -> Self
237    where
238        K: Into<OsString>,
239        V: Into<EnvChange>,
240    {
241        self.env_updates
242            .extend(map.into_iter().map(|(k, v)| (k.into(), v.into())));
243        self
244    }
245
246    /// Returns this command with the map of env updates updated by one key value pair.
247    ///
248    /// If the new key already did exist in the current updates it will replace that
249    /// old key & value.
250    ///
251    /// See [`Command::with_env_updates()`].
252    pub fn with_env_update(
253        mut self,
254        key: impl Into<OsString>,
255        value: impl Into<EnvChange>,
256    ) -> Self {
257        self.env_updates.insert(key.into(), value.into());
258        self
259    }
260
261    /// Returns true if the env of the current process is inherited.
262    ///
263    /// Updates to then environment are applied after the inheritance:
264    ///
265    /// - [`EnvChange::Set`] can be use to override inherited env vars, or
266    ///   add new ones if no variable with given key was inherited
267    /// - [`EnvChange::Remove`] can be used to remove an inherited (or previously added)
268    ///   env variable
269    /// - [`EnvChange::Inherit`] can be used to state a env variable should be inherited
270    ///   even if `inherit_env` is `false`. If `inherit_env` is true this will have no
271    ///   effect.
272    pub fn inherit_env(&self) -> bool {
273        self.inherit_env
274    }
275
276    /// Returns this command with a change to weather or the sub-process will inherit env variables.
277    ///
278    /// See [`Command::inherit_env()`] for how this affects the sub-process env.
279    pub fn with_inherit_env(mut self, do_inherit: bool) -> Self {
280        self.inherit_env = do_inherit;
281        self
282    }
283
284    /// Returns a map with all env variables the sub-process spawned by this command would have
285    /// if the current processes env is not changed.
286    ///
287    /// # Site note about `env::set_var()` problems
288    ///
289    /// Note that if you use `std::env::set_var()` in a multi-threaded setup depending on
290    /// the operating system you run this on this can lead to all kind of problem, including
291    /// unexpected race conditions in some situations (especially if `inherit_env(true)` is
292    /// combined with `EnvChange::Inherit` and multiple variables are changed in another thread
293    /// racing with this function and some but not all are covered by `EnvChange::Inherit`).
294    ///
295    /// Given that [`std::env::set_var()`] should strictly be avoided in a multi-threaded context
296    /// this is seen as an acceptable drawback.
297    ///
298    /// Note that this function + `std::env::set_var()` is not unsafe it might just have a
299    /// very unexpected result. Except if `env::set_var()` + reading env races are inherently
300    /// unsafe on your system, in which case this has nothing to do with this function.
301    pub fn create_expected_env_iter(&self) -> impl Iterator<Item = (Cow<OsStr>, Cow<OsStr>)> {
302        let inherit = if self.inherit_env() {
303            Some(env::vars_os())
304        } else {
305            None
306        };
307
308        return ExpectedEnvIter {
309            self_: self,
310            inherit,
311            update: Some(self.env_updates.iter()),
312        };
313
314        //FIXME[rust/generators] use yield base iterator
315        struct ExpectedEnvIter<'a, Output, Error>
316        where
317            Output: 'static,
318            Error: From<io::Error> + From<UnexpectedExitStatus> + 'static,
319        {
320            self_: &'a Command<Output, Error>,
321            inherit: Option<VarsOs>,
322            update: Option<std::collections::hash_map::Iter<'a, OsString, EnvChange>>,
323        }
324
325        impl<'a, O, E> Iterator for ExpectedEnvIter<'a, O, E>
326        where
327            O: 'static,
328            E: From<io::Error> + From<UnexpectedExitStatus> + 'static,
329        {
330            type Item = (Cow<'a, OsStr>, Cow<'a, OsStr>);
331
332            fn next(&mut self) -> Option<Self::Item> {
333                loop {
334                    fused_opt_iter_next!(&mut self.inherit, |(key, val)| {
335                        match self.self_.env_updates.get(&key) {
336                            Some(_) => continue,
337                            None => return Some((Cow::Owned(key), Cow::Owned(val))),
338                        }
339                    });
340                    fused_opt_iter_next!(&mut self.update, |(key, change)| {
341                        match change {
342                            EnvChange::Set(val) => {
343                                return Some((Cow::Borrowed(&key), Cow::Borrowed(&val)));
344                            }
345                            EnvChange::Inherit => {
346                                // Mostly used if inherit_var is valse in which case we *should* not
347                                // have done aboves loop-part on vars_os. We could "optimize" this to
348                                // handle Inherit in aboves loop if we run that loop, but why add that
349                                // complexity?
350                                if let Some(val) = env::var_os(&key) {
351                                    return Some((Cow::Borrowed(&key), Cow::Owned(val)));
352                                } else {
353                                    continue;
354                                }
355                            }
356                            EnvChange::Remove => {
357                                continue;
358                            }
359                        }
360                    });
361                    return None;
362                }
363            }
364        }
365    }
366
367    /// Return the working directory which will be used instead of the current working directory.
368    ///
369    /// If `None` is returned it means no override is set and the working directory will be inherited
370    /// from the spawning process.
371    pub fn working_directory_override(&self) -> Option<&Path> {
372        self.working_directory_override.as_ref().map(|s| &**s)
373    }
374
375    /// Replaces the working directory override.
376    ///
377    /// Setting it to `None` will unset the override making the spawned
378    /// process inherit the working directory from the spawning process.
379    pub fn with_working_directory_override(
380        mut self,
381        wd_override: Option<impl Into<PathBuf>>,
382    ) -> Self {
383        self.working_directory_override = wd_override.map(Into::into);
384        self
385    }
386
387    /// Return which exit status is treated as success.
388    pub fn expected_exit_status(&self) -> ExitStatus {
389        self.expected_exit_status
390    }
391
392    /// Set which exit status is treated as successful.
393    ///
394    /// **This enables exit status checking even if it
395    ///   was turned of before.**
396    pub fn with_expected_exit_status(self, exit_status: impl Into<ExitStatus>) -> Self {
397        let mut cmd = self.with_check_exit_status(true);
398        cmd.expected_exit_status = exit_status.into();
399        cmd
400    }
401
402    /// Returns true if the exit status is checked before mapping the output(s).
403    pub fn check_exit_status(&self) -> bool {
404        self.check_exit_status
405    }
406
407    /// Sets if the exit status is checked before mapping the output(s).
408    pub fn with_check_exit_status(mut self, val: bool) -> Self {
409        self.check_exit_status = val;
410        self
411    }
412
413    /// Returns true if stdout will be captured.
414    ///
415    /// # Panics
416    ///
417    /// **If called in a `exec_replacement_callback` this will panic.**
418    pub fn will_capture_stdout(&self) -> bool {
419        self.return_settings
420            .as_ref()
421            .expect("Can not be called in a exec_replacement_callback.")
422            .capture_stdout()
423    }
424
425    /// Returns true if stderr will be captured.
426    ///
427    /// # Panics
428    ///
429    /// **If called in a `exec_replacement_callback` this will panic.**
430    pub fn will_capture_stderr(&self) -> bool {
431        self.return_settings
432            .as_ref()
433            .expect("Can not be called in a exec_replacement_callback.")
434            .capture_stderr()
435    }
436
437    /// Run the command, blocking until completion and then mapping the output.
438    ///
439    /// This will:
440    ///
441    /// 1. run the program with the specified arguments and env variables
442    /// 2. capture the necessary outputs as specified by the output mapping
443    /// 3. if exit status checking was not disabled check the exit status and potentially
444    ///    fail.
445    /// 4. if 3 doesn't fail now map captured outputs to a `Result<Output, Error>`
446    ///
447    /// If [`Command::with_exec_replacement_callback()`] is used instead of running the
448    /// program and capturing the output the given callback is called. The callback
449    /// could mock the program execution. The exit status checking and output mapping
450    /// are still done as normal.
451    ///
452    /// # Panics
453    ///
454    /// **This will panic if called in a `exec_replacement_callback`.**
455    pub fn run(mut self) -> Result<Output, Error> {
456        let expected_exit_status = self.expected_exit_status;
457        let check_exit_status = self.check_exit_status;
458        let return_settings = self
459            .return_settings
460            .take()
461            .expect("run recursively called in exec replacing callback");
462        let run_callback = self
463            .run_callback
464            .take()
465            .expect("run recursively called in exec replacing callback");
466
467        let result = run_callback(self, &*return_settings)?;
468
469        if check_exit_status && result.exit_status != expected_exit_status {
470            return Err(UnexpectedExitStatus {
471                got: result.exit_status,
472                expected: expected_exit_status,
473            }
474            .into());
475        } else {
476            let stdout = if return_settings.capture_stdout() {
477                result.stdout
478            } else {
479                debug_assert!(result.stdout.is_none());
480                None
481            };
482            let stderr = if return_settings.capture_stderr() {
483                result.stderr
484            } else {
485                debug_assert!(result.stderr.is_none());
486                None
487            };
488            let exit_status = result.exit_status;
489            return_settings.map_output(stdout, stderr, exit_status)
490        }
491    }
492
493    /// Sets a callback which is called instead of executing the command when running the command.
494    ///
495    /// This is mainly meant to be used for mocking command execution during testing, but can be used for
496    /// other thinks, too. E.g. the current implementation does have a default callback for normally executing
497    /// the command this method was not called.
498    ///
499    ///
500    /// # Implementing Mocks with an exec_replacement_callback
501    ///
502    /// You must not call following methods in the callback:
503    ///
504    /// - [`Command::run()`], recursively calling run will not work.
505    /// - [`Command::will_capture_stdout()`], use the passed in output mapping [`OutputMapping::capture_stdout()`] method instead.
506    /// - [`Command::will_capture_stderr()`], use the passed in output mapping [`OutputMapping::capture_stderr()`] method instead.
507    ///
508    /// An emulation of captured output and exit status is returned as [`ExecResult`] instance:
509    ///
510    /// - Any exit code can be returned including a target specific one,
511    ///   the `From<num> for ExitStatus` implementations are useful here.
512    /// - If  [`OutputMapping::capture_stdout()`] is `true` then [`ExecResult::stdout`] must be `Some`
513    ///   else it must be `None`. Failing to do so will panic on unwrap of debug assertions.
514    /// - If  [`OutputMapping::capture_stdout()`] is `true` then [`ExecResult::stdout`] must be `Some`
515    ///   else it must be `None`. Failing to do so will panic on unwrap of debug assertions.
516    ///
517    /// If used for mocking in tests you already know if stdout/stderr is assumed to (not) be
518    /// captured so you do not need to access [`OutputMapping::capture_stdout()`]/[`OutputMapping::capture_stdout()`].
519    ///
520    /// Settings like env updates and inheritance can be retrieved from the passed in `Command` instance.
521    ///
522    /// # Implement custom subprocess spawning
523    ///
524    /// *Be aware that if you execute the program in the callback you need to make sure the right program, arguments
525    /// stdout/stderr capture setting and env variables are used. Especially note should be taken to how `EnvChange::Inherit`
526    /// is handled.*
527    ///
528    /// The [`Command::create_expected_env_iter()`] method can be used to find what exact env variables
529    /// are expected to be in the sub-process. Clearing the sub-process env and then setting all env vars
530    /// using [`Command::create_expected_env_iter()`] is not the most efficient but most simple and robust
531    /// to changes way to set the env. It's recommended to be used.
532    ///
533    pub fn with_exec_replacement_callback(
534        mut self,
535        callback: impl FnOnce(
536                Self,
537                &dyn OutputMapping<Output = Output, Error = Error>,
538            ) -> Result<ExecResult, io::Error>
539            + 'static,
540    ) -> Self {
541        self.run_callback = Some(Box::new(callback));
542        self
543    }
544}
545
546/// Trait used to configure what [`Command::run()`] returns.
547pub trait OutputMapping: 'static {
548    /// The output produced by this command, if it is run and doesn't fail.
549    type Output: 'static;
550
551    /// The error produced by this command, if it is run and does fail.
552    type Error: 'static;
553
554    /// Return if stdout needs to be captured for this output mapping `map_output` function.
555    ///
556    /// *This should be a pure function only depending on `&self`.*
557    fn capture_stdout(&self) -> bool;
558
559    /// Return if stderr needs to be captured for this output mapping `map_output` function.
560    ///
561    /// *This should be a pure function only depending on `&self`.*
562    fn capture_stderr(&self) -> bool;
563
564    /// The function called once the command's run completed.
565    ///
566    /// This function is used to convert the captured stdout/stderr
567    /// to an instance of the given `Output` type.
568    ///
569    /// If exist code checking is enabled and fails this function will
570    /// not be called.
571    ///
572    /// If it is disabled this function will be called and the implementation
573    /// can still decide to fail due to an unexpected/bad exit status.
574    fn map_output(
575        self: Box<Self>,
576        stdout: Option<Vec<u8>>,
577        stderr: Option<Vec<u8>>,
578        exit_status: ExitStatus,
579    ) -> Result<Self::Output, Self::Error>;
580}
581
582/// The command failed due to an unexpected exit status.
583///
584/// By default this means the exit status was not 0, but
585/// this can be reconfigured.
586#[derive(Debug, Error)]
587#[error("Unexpected exit status. Got: {got}, Expected: {expected}")]
588pub struct UnexpectedExitStatus {
589    got: ExitStatus,
590    expected: ExitStatus,
591}
592
593/// A ExitStatus type similar to `std::process::ExitStatus` but which can be created (e.g. for testing).
594///
595/// # Display
596///
597/// If there is an exit code it will always be displayed as hexadecimal.
598/// This is done because of two reasons:
599///
600/// - Some platforms allow rather large exit codes which are just very unreadable (and unrecognizable) in decimal formatting.
601/// - The hex format is always bit based which removes confusions around differences between targets having signed and unsigned
602///   exit codes. The drawback is that on platforms which do allow negative exit codes you need to convert the number not just
603///   from hex to decimal but also consider signing wrt. the max supported unsigned size on that platform.
604///
605/// An target specific exit status is displayed in a target specific way.
606/// The non os specific fallback defaults to displaying `NO_exit_status`.
607/// A signal termination exit status on unix will be displayed as e.g.
608/// `signal(9)`.
609///
610///
611/// # Os Support
612///
613/// For now part of this type are only supported for targets of the os families
614/// windows and unix(-like).
615///
616/// If you need support for _any_ other OS feel free to open an issue, I will
617/// add the necessary code path for the methods which are not OS independent
618/// then.
619///
620/// Currently this only affects the [`ExitStatus::successful()`] method.
621///
622/// # Why not `std::process::ExitStatus`?
623///
624/// The standard library and this library have different design goals, most
625/// importantly this library can introduce braking changes while the standard
626/// library ones can't really do so.
627///
628/// Major differences include:
629///
630/// - Just one enum instead of an `.exit_status() -> Option<i32>` accessor.
631/// - Implements `PartialEq<RHS>` for various numbers making testing easier.
632/// - Has a platform independent constructor, `std::process::ExitStatus` has
633///   various platform specific constructors.
634/// - Uses `i64` as exit code to more correctly represents exits codes (see below).
635///
636/// ## Incompatibilities and limitations.
637///
638/// Due to the current structures a various exit codes can be constructed which
639/// are not possible to appear on the target you are currently compiling against.
640/// **This is already true for the std implementation, but with slightly less
641/// constraints in our case.** For example if you target linux you can still
642/// create a exit code > 0xFF but in linux no exit code > 0xFF can be returned (
643/// furthermore returning any exit code > 127 is a cause of unexpected problems
644/// and must be avoided).
645///
646/// Furthermore `std::process::Command` returning a i32 code is a major problem
647/// as it's incompatible with various platforms:
648///
649/// - Windows has a `u32`! exit code, rust std's Command does reinterpret
650///   it as `i32` when returning it which in some cases lead to negative
651///   exit codes even through there *are not negative exit codes on windows*.
652///   Furthermore Fushisa does have a i64 exit status which they currently
653///   can't handle at all. *Be aware that this library still uses
654///   `std::process::Command` internally and a such can't handle this either*.
655///   But we do "fix" the exit code so that an exit code of `u32::MAX` is still
656///   `u32::MAX` and not `-1`!.
657///
658#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
659pub enum ExitStatus {
660    /// The process exited with an exit code.
661    ///
662    /// As this allows any i64 this allows you to create an exit status which can not
663    /// appear on the current target. (This is also possible with the standard libraries
664    /// `ExitStatus`). This makes testing easier and allows you to test cases of exit
665    /// codes which can't appear on your but other platforms.
666    ///
667    /// # Differences to `std::process::ExitStatus`
668    ///
669    /// This uses a `i64` as this allows a more correct representation of exit codes.
670    ///
671    /// *On windows a exit code > `i32::MAX` will be correctly be represented as such
672    /// instead of wrongly being displayed as negative number.*
673    Code(i64),
674
675    /// An exit status which isn't a simple exit code was returned.
676    ///
677    /// On unix if a process was directly terminated via an signal no exit code was
678    /// set but the signal causing the exit is returned (encoded with other values
679    /// in the raw exit status).
680    ///
681    /// Rust represents this separately as depending on the exact unix-like operating
682    /// system it might be encoded in different ways, in some cases instead of a integer
683    /// encoding the status a struct with multiple fields is returned, as such there
684    /// is no correct or reliable way to encode exit an status just as an number.
685    ///
686    /// Be aware that for testability [`OpaqueOsExitStatus`] can be created on all platforms
687    /// even through e.g. on windows there are only exit codes! Note that the exact inner
688    /// implementation of [`OpaqueOsExitStatus`] is platform dependent, but it implements
689    /// [`arbitrary_default()`](OpaqueOsExitStatus::target_specific_default())
690    OsSpecific(OpaqueOsExitStatus),
691}
692
693impl ExitStatus {
694    /// Returns true if the command did succeed.
695    ///
696    /// As not all operating systems use 0 == success we need to have platform
697    /// specific code for all of them. Which is infeasible and as such this is
698    /// only enabled on the unix and window target family. (Note that windows
699    /// and unix are currently the only target families as e.g. linux, all BSD's,
700    /// OsX, iOs are unix-like enough to count as part of the unix family).
701    #[cfg(any(window, unix))]
702    pub fn successful(&self) -> bool {
703        match self {
704            Self::Code(code) if *code == 0 => true,
705            _ => false,
706        }
707    }
708}
709
710impl Display for ExitStatus {
711    fn fmt(&self, fter: &mut fmt::Formatter) -> fmt::Result {
712        match self {
713            Self::Code(code) => write!(fter, "0x{:X}", code),
714            Self::OsSpecific(alt) => Display::fmt(alt, fter),
715        }
716    }
717}
718
719impl Default for ExitStatus {
720    fn default() -> Self {
721        Self::Code(0)
722    }
723}
724
725impl From<OpaqueOsExitStatus> for ExitStatus {
726    fn from(ooes: OpaqueOsExitStatus) -> Self {
727        ExitStatus::OsSpecific(ooes)
728    }
729}
730
731macro_rules! impl_from_and_partial_eq_for_fitting_int {
732    ($($int:ty),*) => ($(
733        impl From<$int> for ExitStatus {
734            fn from(code: $int) -> Self {
735                Self::Code(code as _)
736            }
737        }
738
739        impl PartialEq<$int> for ExitStatus {
740            fn eq(&self, other: &$int) -> bool {
741                match self {
742                    Self::Code(code) => *code == *other as i64,
743                    Self::OsSpecific(_) => false,
744                }
745            }
746        }
747    )*);
748}
749
750impl_from_and_partial_eq_for_fitting_int!(u8, i8, u16, i16, u32, i32, i64);
751/// A platform specific opaque exit status.
752///
753/// An exit status which is not an exit code, e.g.
754/// on unix the signal which terminated an process
755/// preventing it from exiting with an exit status.
756///
757/// **Warning: Besides [`OpaqueOsExitStatus::target_specific_default()`]
758/// all other methods only exist on _some_ targets but not all.** As such
759/// using them can lead to code which only compiles on some targets.
760#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
761pub struct OpaqueOsExitStatus {
762    #[cfg(not(unix))]
763    _priv: (),
764    #[cfg(unix)]
765    signal: i32,
766}
767
768impl OpaqueOsExitStatus {
769    /// Creates a instance of this type.
770    ///
771    /// This is meant for allowing non-platform specific tests which
772    /// handle the case of a non exit code process exit status.
773    ///
774    /// Platform specific tests likely still are needed as what
775    /// this type means is platform specific.
776    ///
777    /// This will always create the same default value but it's
778    /// a target_specific_value *and* it's picked arbitrary so
779    /// it's not really appropriately to implement [`Default`].
780    /// (To make clear why it isn't consider `u32` would default
781    /// to `246` or similar arbitrary value.)
782    pub fn target_specific_default() -> Self {
783        Self {
784            #[cfg(not(unix))]
785            _priv: (),
786            #[cfg(unix)]
787            signal: 9,
788        }
789    }
790
791    /// Return the signal number which did lead to the process termination.
792    #[cfg(unix)]
793    pub fn signal_number(&self) -> i32 {
794        self.signal
795    }
796
797    /// Create a unix [`OpaqueOsExitStatus`] instance based on the signal code
798    /// causing the non exit code termination.
799    ///
800    /// Like some other aspects you can define (and test) unrealistic signal numbers.
801    /// IMHO this is better (more simple, flexible etc.) then to have a result which
802    /// is potentially target dependent or a implicit target dependent bit masking.
803    ///
804    // E.g. on linux and most (all) unix it's limited to 7 bit (&0x7f) but on at least
805    // OpenBSD the value 0x7F is reserved and doesn't count as signal (the macro for
806    // testing if it exited with an signal excludes it). Also in any case 0 is not a
807    // valid signal either.
808    //
809    // POSIX defines signals as `int` and with this more or less as i32, but this seems to
810    // be because of practical reasons i.e. bitmasking a i32 produces a i32. I do not think
811    // there are any negative signals at all, nor do there seem to be any platforms with more
812    // than a handful of valid signals.
813    #[cfg(unix)]
814    pub fn from_signal_number(signal: i32) -> Self {
815        Self { signal }
816    }
817}
818
819impl Display for OpaqueOsExitStatus {
820    fn fmt(&self, fter: &mut fmt::Formatter) -> fmt::Result {
821        #[cfg(not(unix))]
822        {
823            fter.write_str("NO_EXIT_CODE")
824        }
825        #[cfg(unix)]
826        {
827            write!(fter, "signal({})", self.signal)
828        }
829    }
830}
831
832/// Used to determine how a env variable should be updated.
833#[derive(Debug, Clone, PartialEq)]
834pub enum EnvChange {
835    /// Remove the env value if it normally would have been set
836    ///
837    /// (e.g. because of inherited environment)
838    Remove,
839
840    /// Make sure the env variable will have given value in the sub-process.
841    Set(OsString),
842
843    /// Make sure the env variable is inherited from the process spawning the sub-process.
844    ///
845    /// If environment inheritance is disabled (e.g. using `with_inherit_env(false)`) this
846    /// will cause given values to still be inherited anyway.
847    ///
848    /// If environment inheritance is enabled this won't have any effect.
849    Inherit,
850}
851
852impl From<&Self> for EnvChange {
853    fn from(borrow: &Self) -> Self {
854        borrow.clone()
855    }
856}
857impl From<&OsString> for EnvChange {
858    fn from(val: &OsString) -> Self {
859        EnvChange::Set(val.clone())
860    }
861}
862
863impl From<OsString> for EnvChange {
864    fn from(val: OsString) -> Self {
865        EnvChange::Set(val)
866    }
867}
868
869impl From<&OsStr> for EnvChange {
870    fn from(val: &OsStr) -> Self {
871        EnvChange::Set(val.into())
872    }
873}
874
875impl From<String> for EnvChange {
876    fn from(val: String) -> Self {
877        EnvChange::Set(val.into())
878    }
879}
880
881impl From<&str> for EnvChange {
882    fn from(val: &str) -> Self {
883        EnvChange::Set(val.into())
884    }
885}
886
887/// Type used for `exec_replacement_callback` to return mocked output and exit status.
888#[derive(Debug, Default)]
889pub struct ExecResult {
890    /// The exit status the process did exit with.
891    pub exit_status: ExitStatus,
892
893    /// The stdout output captured during sub-process execution (if any).
894    ///
895    /// This must be `Some` if `stdout` is expected to be captured, it must
896    /// be `None` if it's expected to not be captured.
897    pub stdout: Option<Vec<u8>>,
898
899    /// The stderr output captured during sub-process execution (if any).
900    ///
901    /// This must be `Some` if `stderr` is expected to be captured, it must
902    /// be `None` if it's expected to not be captured.
903    pub stderr: Option<Vec<u8>>,
904}
905
906#[cfg(test)]
907mod tests {
908    use super::*;
909    use proptest::prelude::*;
910    use thiserror::Error;
911
912    #[derive(Debug, Error)]
913    enum TestCommandError {
914        #[error(transparent)]
915        Io(#[from] io::Error),
916
917        #[error(transparent)]
918        UnexpectedExitStatus(#[from] UnexpectedExitStatus),
919
920        #[error("TestCase error: {0}")]
921        Prop(TestCaseError),
922    }
923
924    impl From<TestCaseError> for TestCommandError {
925        fn from(prop_err: TestCaseError) -> Self {
926            Self::Prop(prop_err)
927        }
928    }
929
930    impl TestCommandError {
931        pub fn unwrap_prop(self) -> TestCaseError {
932            match self {
933                Self::Io(err) => panic!("unexpected io error: {:?}", err),
934                Self::UnexpectedExitStatus(err) => panic!("unexpected exit status: {:?}", err),
935                Self::Prop(prop_err) => return prop_err,
936            }
937        }
938    }
939
940    struct TestOutputMapping {
941        capture_stdout: bool,
942        capture_stderr: bool,
943    }
944
945    impl OutputMapping for TestOutputMapping {
946        type Output = bool;
947        type Error = TestCommandError;
948
949        fn capture_stdout(&self) -> bool {
950            self.capture_stdout
951        }
952        fn capture_stderr(&self) -> bool {
953            self.capture_stderr
954        }
955
956        fn map_output(
957            self: Box<Self>,
958            stdout: Option<Vec<u8>>,
959            stderr: Option<Vec<u8>>,
960            _exit_status: super::ExitStatus,
961        ) -> Result<Self::Output, Self::Error> {
962            (|| {
963                prop_assert_eq!(stdout.is_some(), self.capture_stdout());
964                prop_assert_eq!(stderr.is_some(), self.capture_stderr());
965                Ok(())
966            })()?;
967            Ok(true)
968        }
969    }
970
971    mod Command {
972        #![allow(non_snake_case)]
973
974        mod new {
975            use super::super::super::*;
976            use proptest::prelude::*;
977
978            #[test]
979            fn comp_can_be_created_using_str_string_os_str_or_os_string() {
980                Command::new("ls", ReturnNothing);
981                Command::new("ls".to_owned(), ReturnNothing);
982                Command::new(OsString::from("ls"), ReturnNothing);
983                Command::new(OsStr::new("ls"), ReturnNothing);
984            }
985
986            #[test]
987            fn comp_when_creating_command_different_capture_modes_can_be_used() {
988                Command::new("foo", ReturnNothing);
989                Command::new("foo", ReturnStdout);
990                Command::new("foo", ReturnStderr);
991                Command::new("foo", ReturnStdoutAndErr);
992            }
993
994            proptest! {
995                #[test]
996                fn the_used_program_can_be_queried(s in any::<OsString>()) {
997                    let s = OsStr::new(&*s);
998                    let cmd = Command::new(s, ReturnNothing);
999                    prop_assert_eq!(&*cmd.program(), s)
1000                }
1001            }
1002        }
1003
1004        mod arguments {
1005            use super::super::super::*;
1006            use proptest::prelude::*;
1007            use std::{collections::HashSet, iter};
1008
1009            #[test]
1010            fn default_arguments_are_empty() {
1011                let cmd = Command::new("foo", ReturnNothing);
1012                assert!(cmd.arguments().is_empty());
1013            }
1014
1015            #[test]
1016            fn comp_arguments_can_be_set_from_iterables() {
1017                Command::new("foo", ReturnNothing).with_arguments(Vec::<OsString>::new());
1018                Command::new("foo", ReturnNothing).with_arguments(HashSet::<OsString>::new());
1019                Command::new("foo", ReturnNothing).with_arguments(&[] as &[OsString]);
1020            }
1021
1022            proptest! {
1023                #[test]
1024                fn new_arguments_can_be_added(
1025                    cmd in any::<OsString>(),
1026                    argument in any::<OsString>(),
1027                    arguments in proptest::collection::vec(any::<OsString>(), 0..5),
1028                    arguments2 in proptest::collection::vec(any::<OsString>(), 0..5)
1029                ) {
1030                    let cmd = OsStr::new(&*cmd);
1031                    let cmd = Command::new(cmd, ReturnNothing)
1032                        .with_arguments(&arguments);
1033                    prop_assert_eq!(cmd.arguments(), &arguments);
1034                    let cmd = cmd.with_argument(&argument);
1035                    prop_assert_eq!(
1036                        cmd.arguments().iter().collect::<Vec<_>>(),
1037                        arguments.iter().chain(iter::once(&argument)).collect::<Vec<_>>()
1038                    );
1039                    let cmd = cmd.with_arguments(&arguments2);
1040                    prop_assert_eq!(
1041                        cmd.arguments().iter().collect::<Vec<_>>(),
1042                        arguments.iter()
1043                            .chain(iter::once(&argument))
1044                            .chain(arguments2.iter())
1045                            .collect::<Vec<_>>()
1046                    );
1047                }
1048            }
1049        }
1050
1051        mod run {
1052            use super::super::super::*;
1053
1054            #[test]
1055            fn run_can_lead_to_and_io_error() {
1056                let res = Command::new("foo", ReturnNothing)
1057                    .with_exec_replacement_callback(|_, _| {
1058                        Err(io::Error::new(io::ErrorKind::Other, "random"))
1059                    })
1060                    .run();
1061
1062                res.unwrap_err();
1063            }
1064
1065            #[test]
1066            fn return_no_error_if_the_command_has_zero_exit_status() {
1067                let res = Command::new("foo", ReturnNothing)
1068                    .with_exec_replacement_callback(move |_, _| {
1069                        Ok(ExecResult {
1070                            exit_status: 0.into(),
1071                            ..Default::default()
1072                        })
1073                    })
1074                    .run();
1075
1076                res.unwrap();
1077            }
1078        }
1079
1080        mod ReturnSetting {
1081            use super::super::super::*;
1082            use super::super::TestOutputMapping;
1083            use proptest::prelude::*;
1084
1085            #[test]
1086            fn comp_command_must_only_be_generic_over_the_output() {
1087                if false {
1088                    let mut _cmd = Command::new("foo", ReturnNothing);
1089                    _cmd = Command::new("foo", ReturnNothingAlt);
1090                }
1091
1092                //---
1093                struct ReturnNothingAlt;
1094                impl OutputMapping for ReturnNothingAlt {
1095                    type Output = ();
1096                    type Error = CommandExecutionError;
1097                    fn capture_stdout(&self) -> bool {
1098                        false
1099                    }
1100                    fn capture_stderr(&self) -> bool {
1101                        false
1102                    }
1103                    fn map_output(
1104                        self: Box<Self>,
1105                        _stdout: Option<Vec<u8>>,
1106                        _stderr: Option<Vec<u8>>,
1107                        _exit_status: ExitStatus,
1108                    ) -> Result<Self::Output, Self::Error> {
1109                        unimplemented!()
1110                    }
1111                }
1112            }
1113
1114            #[test]
1115            fn allow_custom_errors() {
1116                let _result: MyError = Command::new("foo", ReturnError)
1117                    .with_exec_replacement_callback(|_, _| {
1118                        Ok(ExecResult {
1119                            exit_status: 0.into(),
1120                            ..Default::default()
1121                        })
1122                    })
1123                    .run()
1124                    .unwrap_err();
1125
1126                //------------
1127                struct ReturnError;
1128                impl OutputMapping for ReturnError {
1129                    type Output = ();
1130                    type Error = MyError;
1131                    fn capture_stdout(&self) -> bool {
1132                        false
1133                    }
1134                    fn capture_stderr(&self) -> bool {
1135                        false
1136                    }
1137                    fn map_output(
1138                        self: Box<Self>,
1139                        _stdout: Option<Vec<u8>>,
1140                        _stderr: Option<Vec<u8>>,
1141                        _exit_status: ExitStatus,
1142                    ) -> Result<Self::Output, Self::Error> {
1143                        Err(MyError::BarFoot)
1144                    }
1145                }
1146                #[derive(Debug, Error)]
1147                enum MyError {
1148                    #[error("FooBar")]
1149                    BarFoot,
1150
1151                    #[error(transparent)]
1152                    Io(#[from] io::Error),
1153
1154                    #[error(transparent)]
1155                    UnexpectedExitStatus(#[from] UnexpectedExitStatus),
1156                }
1157            }
1158
1159            #[should_panic]
1160            #[test]
1161            fn returning_stdout_which_should_not_be_captured_triggers_a_debug_assertion() {
1162                let _ = Command::new("foo", ReturnNothing)
1163                    .with_check_exit_status(false)
1164                    .with_exec_replacement_callback(|_, _| {
1165                        Ok(ExecResult {
1166                            exit_status: 1.into(),
1167                            stdout: Some(Vec::new()),
1168                            ..Default::default()
1169                        })
1170                    })
1171                    .run();
1172            }
1173
1174            #[should_panic]
1175            #[test]
1176            fn returning_stderr_which_should_not_be_captured_triggers_a_debug_assertion() {
1177                let _ = Command::new("foo", ReturnNothing)
1178                    .with_check_exit_status(false)
1179                    .with_exec_replacement_callback(|_, _| {
1180                        Ok(ExecResult {
1181                            exit_status: 1.into(),
1182                            stderr: Some(Vec::new()),
1183                            ..Default::default()
1184                        })
1185                    })
1186                    .run();
1187            }
1188
1189            proptest! {
1190                #[test]
1191                fn only_pass_stdout_stderr_to_map_output_if_return_settings_indicate_they_capture_it(
1192                    capture_stdout in proptest::bool::ANY,
1193                    capture_stderr in proptest::bool::ANY
1194                ) {
1195                    let res = Command::new("foo", TestOutputMapping { capture_stdout, capture_stderr })
1196                        .with_exec_replacement_callback(move |_,_| {
1197                            Ok(ExecResult {
1198                                exit_status: 0.into(),
1199                                stdout: if capture_stdout { Some(Vec::new()) } else { None },
1200                                stderr: if capture_stderr { Some(Vec::new()) } else { None }
1201                            })
1202                        })
1203                        .run()
1204                        .map_err(|e| e.unwrap_prop())?;
1205
1206                    assert!(res);
1207                }
1208
1209                #[test]
1210                fn command_provides_a_getter_to_check_if_stdout_and_err_will_be_captured(
1211                    capture_stdout in proptest::bool::ANY,
1212                    capture_stderr in proptest::bool::ANY
1213                ) {
1214                    let cmd = Command::new("foo", TestOutputMapping { capture_stdout, capture_stderr });
1215                    prop_assert_eq!(cmd.will_capture_stdout(), capture_stdout);
1216                    prop_assert_eq!(cmd.will_capture_stderr(), capture_stderr);
1217                }
1218
1219                #[test]
1220                fn capture_hints_are_available_in_the_callback(
1221                    capture_stdout in proptest::bool::ANY,
1222                    capture_stderr in proptest::bool::ANY
1223                ) {
1224                    Command::new("foo", TestOutputMapping { capture_stdout, capture_stderr })
1225                        .with_exec_replacement_callback(move |_cmd, return_settings| {
1226                            assert_eq!(return_settings.capture_stdout(), capture_stdout);
1227                            assert_eq!(return_settings.capture_stderr(), capture_stderr);
1228                            Ok(ExecResult {
1229                                exit_status: 0.into(),
1230                                stdout: if capture_stdout { Some(Vec::new()) } else { None },
1231                                stderr: if capture_stderr { Some(Vec::new()) } else { None }
1232                            })
1233                        })
1234                        .run()
1235                        .unwrap();
1236                }
1237            }
1238        }
1239        mod environment {
1240            use super::super::super::*;
1241            use proptest::prelude::*;
1242
1243            #[test]
1244            fn by_default_no_environment_updates_are_done() {
1245                let cmd = Command::new("foo", ReturnNothing);
1246                assert!(cmd.env_updates().is_empty());
1247            }
1248
1249            #[test]
1250            fn create_expected_env_iter_includes_the_current_env_by_default() {
1251                let process_env = env::vars_os()
1252                    .into_iter()
1253                    .map(|(k, v)| (Cow::Owned(k), Cow::Owned(v)))
1254                    .collect::<HashMap<_, _>>();
1255                let cmd = Command::new("foo", ReturnNothing);
1256                let created_map = cmd.create_expected_env_iter().collect::<HashMap<_, _>>();
1257                assert_eq!(process_env, created_map);
1258            }
1259
1260            #[test]
1261            fn by_default_env_is_inherited() {
1262                let cmd = Command::new("foo", ReturnNothing);
1263                assert_eq!(cmd.inherit_env(), true);
1264                //FIXME fluky if there is no single ENV variable set
1265                assert_ne!(cmd.create_expected_env_iter().count(), 0);
1266            }
1267
1268            #[test]
1269            fn inheritance_of_env_variables_can_be_disabled() {
1270                let cmd = Command::new("foo", ReturnNothing).with_inherit_env(false);
1271                assert_eq!(cmd.inherit_env(), false);
1272                assert_eq!(cmd.create_expected_env_iter().count(), 0);
1273            }
1274
1275            proptest! {
1276                #[test]
1277                fn new_env_variables_can_be_added(
1278                    cmd in any::<OsString>(),
1279                    variable in any::<OsString>(),
1280                    value in any::<OsString>(),
1281                    map1 in proptest::collection::hash_map(
1282                        any::<OsString>(),
1283                        any::<OsString>().prop_map(|s| EnvChange::Set(s)),
1284                        0..4
1285                    ),
1286                    map2 in proptest::collection::hash_map(
1287                        any::<OsString>(),
1288                        any::<OsString>().prop_map(|s| EnvChange::Set(s)),
1289                        0..4
1290                    ),
1291                ) {
1292                    let cmd = Command::new(cmd, ReturnNothing)
1293                        .with_env_updates(&map1);
1294
1295                    prop_assert_eq!(cmd.env_updates(), &map1);
1296
1297                    let cmd = cmd.with_env_update(&variable, &value);
1298
1299                    let mut n_map = map1.clone();
1300                    n_map.insert(variable, EnvChange::Set(value));
1301                    prop_assert_eq!(cmd.env_updates(), &n_map);
1302
1303                    let cmd = cmd.with_env_updates(&map2);
1304
1305                    for (key, value) in &map2 {
1306                        n_map.insert(key.into(), value.into());
1307                    }
1308                    prop_assert_eq!(cmd.env_updates(), &n_map);
1309                }
1310
1311
1312                //FIXME on CI this test can leak secrets if it fails
1313                #[test]
1314                fn env_variables_can_be_set_to_be_removed_from_inherited_env(
1315                    cmd in any::<OsString>(),
1316                    rem_key in proptest::sample::select(env::vars_os().map(|(k,_v)| k).collect::<Vec<_>>())
1317                ) {
1318                    let cmd = Command::new(cmd, ReturnNothing).with_env_update(rem_key.clone(), EnvChange::Remove);
1319                    prop_assert_eq!(cmd.env_updates().get(&rem_key), Some(&EnvChange::Remove));
1320
1321                    let produced_env = cmd.create_expected_env_iter()
1322                        .map(|(k,v)| (k.into_owned(), v.into_owned()))
1323                        .collect::<HashMap<OsString, OsString>>();
1324
1325                    prop_assert_eq!(produced_env.get(&rem_key), None);
1326                }
1327
1328                //FIXME on CI this test can leak secrets if it fails
1329                #[test]
1330                fn env_variables_can_be_set_to_be_replaced_from_inherited_env(
1331                    cmd in any::<OsString>(),
1332                    rem_key in proptest::sample::select(env::vars_os().map(|(k,_v)| k).collect::<Vec<_>>()),
1333                    replacement in any::<OsString>()
1334                ) {
1335                    let cmd = Command::new(cmd, ReturnNothing).with_env_update(rem_key.clone(), EnvChange::Set(replacement.clone()));
1336                    let expect = EnvChange::Set(replacement.clone());
1337                    prop_assert_eq!(cmd.env_updates().get(&rem_key), Some(&expect));
1338                    let produced_env = cmd.create_expected_env_iter()
1339                        .map(|(k,v)| (k.into_owned(), v.into_owned()))
1340                        .collect::<HashMap<OsString, OsString>>();
1341
1342                    prop_assert_eq!(produced_env.get(&rem_key), Some(&replacement));
1343                }
1344
1345                //FIXME on CI this test can leak secrets if it fails
1346                #[test]
1347                fn env_variables_can_be_set_to_inherit_even_if_inheritance_is_disabled(
1348                    cmd in any::<OsString>(),
1349                    inherit in proptest::sample::select(env::vars_os().map(|(k,_v)| k).collect::<Vec<_>>()),
1350                )  {
1351                    let expected_val = env::var_os(&inherit);
1352                    let cmd = Command::new(cmd, ReturnNothing)
1353                        .with_inherit_env(false)
1354                        .with_env_update(&inherit, EnvChange::Inherit);
1355
1356                    assert_eq!(cmd.create_expected_env_iter().count(), 1);
1357                    let got_value = cmd.create_expected_env_iter().find(|(k,_v)| &*k==&*inherit)
1358                        .map(|(_k,v)| v);
1359                    assert_eq!(
1360                        expected_val.as_ref().map(|v|&**v),
1361                        got_value.as_ref().map(|v|&**v)
1362                    );
1363                }
1364
1365                #[test]
1366                fn env_variables_can_be_set_to_inherit_even_if_inheritance_is_disabled_2(
1367                    cmd in any::<OsString>(),
1368                    inherit in proptest::sample::select(env::vars_os().map(|(k,_v)| k).collect::<Vec<_>>()),
1369                )  {
1370                    let expected_val = env::var_os(&inherit);
1371                    let cmd = Command::new(cmd, ReturnNothing)
1372                        .with_env_update(&inherit, EnvChange::Inherit)
1373                        .with_inherit_env(false);
1374
1375                    assert_eq!(cmd.create_expected_env_iter().count(), 1);
1376                    let got_value = cmd.create_expected_env_iter().find(|(k,_v)| &*k==&*inherit)
1377                        .map(|(_k,v)| v);
1378                    assert_eq!(
1379                        expected_val.as_ref().map(|v|&**v),
1380                        got_value.as_ref().map(|v|&**v)
1381                    );
1382                }
1383
1384                //FIXME on CI this test can leak secrets if it fails
1385                #[test]
1386                fn setting_inherit_does_not_affect_anything_if_we_anyway_inherit_all(
1387                    cmd in any::<OsString>(),
1388                    pointless_inherit in proptest::sample::select(env::vars_os().map(|(k,_v)| k).collect::<Vec<_>>()),
1389                ) {
1390                    const NON_EXISTING_VAR_KEY: &'static str = "____MAPPED_COMMAND__THIS_SHOULD_NOT_EXIST_AS_ENV_VARIABLE____";
1391                    assert_eq!(env::var_os(NON_EXISTING_VAR_KEY), None);
1392
1393                    let expected_values = env::vars_os()
1394                        .map(|(k,v)| (Cow::Owned(k), Cow::Owned(v)))
1395                        .collect::<HashMap<_,_>>();
1396
1397                    let cmd = Command::new(cmd, ReturnNothing)
1398                        .with_env_update(&pointless_inherit, EnvChange::Inherit)
1399                        .with_env_update(NON_EXISTING_VAR_KEY, EnvChange::Inherit);
1400
1401                    let values = cmd.create_expected_env_iter().collect::<HashMap<_,_>>();
1402
1403                    assert!(!values.contains_key(OsStr::new(NON_EXISTING_VAR_KEY)));
1404                    assert_eq!(expected_values.len(), values.len());
1405                    assert_eq!(
1406                        expected_values.get(&pointless_inherit),
1407                        values.get(&*pointless_inherit)
1408                    );
1409
1410                }
1411            }
1412        }
1413
1414        mod working_directory {
1415            use super::super::super::*;
1416            use crate::utils::opt_arbitrary_path_buf;
1417            use proptest::prelude::*;
1418
1419            #[test]
1420            fn by_default_no_explicit_working_directory_is_set() {
1421                let cmd = Command::new("foo", ReturnNothing);
1422                assert_eq!(cmd.working_directory_override(), None);
1423            }
1424
1425            proptest! {
1426                #[test]
1427                fn the_working_directory_can_be_changed(
1428                    cmd in any::<OsString>(),
1429                    wd_override in opt_arbitrary_path_buf(),
1430                    wd_override2 in opt_arbitrary_path_buf()
1431                ) {
1432                    let cmd = Command::new(cmd, ReturnNothing)
1433                        .with_working_directory_override(wd_override.as_ref());
1434
1435                    assert_eq!(cmd.working_directory_override(), wd_override.as_ref().map(|i|&**i));
1436
1437                    let cmd = cmd.with_working_directory_override(wd_override2.as_ref());
1438                    assert_eq!(cmd.working_directory_override(), wd_override2.as_ref().map(|i|&**i));
1439                }
1440            }
1441        }
1442
1443        mod exit_status_checking {
1444            use super::super::super::*;
1445            use proptest::prelude::*;
1446
1447            #[test]
1448            fn by_default_the_expected_exit_status_is_0() {
1449                let cmd = Command::new("foo", ReturnNothing);
1450                assert_eq!(cmd.expected_exit_status(), 0);
1451            }
1452
1453            #[test]
1454            fn by_default_exit_status_checking_is_enabled() {
1455                let cmd = Command::new("foo", ReturnNothing);
1456                assert_eq!(cmd.check_exit_status(), true);
1457            }
1458
1459            #[test]
1460            fn setting_check_exit_status_to_false_disables_it() {
1461                Command::new("foo", ReturnNothing)
1462                    .with_check_exit_status(false)
1463                    .with_exec_replacement_callback(|_, _| {
1464                        Ok(ExecResult {
1465                            exit_status: 1.into(),
1466                            ..Default::default()
1467                        })
1468                    })
1469                    .run()
1470                    .unwrap();
1471            }
1472
1473            #[test]
1474            fn you_can_expect_no_exit_status_to_be_returned() {
1475                let cmd = Command::new("foo", ReturnNothing).with_expected_exit_status(
1476                    ExitStatus::OsSpecific(OpaqueOsExitStatus::target_specific_default()),
1477                );
1478
1479                assert_eq!(
1480                    cmd.expected_exit_status(),
1481                    ExitStatus::OsSpecific(OpaqueOsExitStatus::target_specific_default())
1482                );
1483            }
1484
1485            #[test]
1486            fn setting_the_expected_exit_status_will_enable_checking() {
1487                let cmd = Command::new("foo", ReturnNothing)
1488                    .with_check_exit_status(false)
1489                    .with_expected_exit_status(0);
1490
1491                assert_eq!(cmd.check_exit_status(), true);
1492            }
1493
1494            proptest! {
1495                #[test]
1496                fn return_an_error_if_the_command_has_non_zero_exit_status(
1497                    cmd in any::<OsString>(),
1498                    exit_status in prop_oneof!(..0, 1..).prop_map(ExitStatus::from)
1499                ) {
1500                    let res = Command::new(cmd, ReturnNothing)
1501                        .with_exec_replacement_callback(move |_,_| {
1502                            Ok(ExecResult {
1503                                exit_status,
1504                                ..Default::default()
1505                            })
1506                        })
1507                        .run();
1508
1509                    res.unwrap_err();
1510                }
1511
1512                #[test]
1513                fn replacing_the_expected_exit_status_causes_error_on_different_exit_status(
1514                    exit_status in -5..6,
1515                    offset in prop_oneof!(-100..0, 1..101)
1516                ) {
1517                    let res = Command::new("foo", ReturnNothing)
1518                        .with_expected_exit_status(exit_status)
1519                        .with_exec_replacement_callback(move |cmd,_| {
1520                            assert_eq!(cmd.expected_exit_status(), exit_status);
1521                            Ok(ExecResult {
1522                                exit_status: ExitStatus::from(exit_status + offset),
1523                                ..Default::default()
1524                            })
1525                        })
1526                        .run();
1527
1528                    match res {
1529                        Err(CommandExecutionError::UnexpectedExitStatus(UnexpectedExitStatus {got, expected})) => {
1530                            assert_eq!(expected, exit_status);
1531                            assert_eq!(got, exit_status+offset);
1532                        },
1533                        _ => panic!("Unexpected Result: {:?}", res)
1534                    }
1535                }
1536
1537                #[test]
1538                fn exit_status_checking_can_be_disabled_and_enabled(
1539                    change1 in proptest::bool::ANY,
1540                    change2 in proptest::bool::ANY,
1541                ) {
1542                    let cmd = Command::new("foo", ReturnNothing)
1543                        .with_check_exit_status(change1);
1544
1545                    assert_eq!(cmd.check_exit_status(), change1);
1546
1547                    let cmd = cmd.with_check_exit_status(change2);
1548                    assert_eq!(cmd.check_exit_status(), change2);
1549                }
1550
1551            }
1552        }
1553
1554        mod exec_replacement_callback {
1555            use std::{cell::RefCell, rc::Rc};
1556
1557            use super::super::super::*;
1558
1559            #[test]
1560            fn program_execution_can_be_replaced_with_an_callback() {
1561                let was_run = Rc::new(RefCell::new(false));
1562                let was_run_ = was_run.clone();
1563                let cmd = Command::new("some_cmd", ReturnStdoutAndErr)
1564                    .with_exec_replacement_callback(move |for_cmd, _| {
1565                        *(*was_run_).borrow_mut() = true;
1566                        assert_eq!(&*for_cmd.program(), "some_cmd");
1567                        Ok(ExecResult {
1568                            exit_status: 0.into(),
1569                            stdout: Some("result=12".to_owned().into()),
1570                            stderr: Some(Vec::new()),
1571                        })
1572                    });
1573
1574                let res = cmd.run().unwrap();
1575                assert_eq!(*was_run.borrow_mut(), true);
1576                assert_eq!(&*res.stdout, "result=12".as_bytes());
1577                assert_eq!(&*res.stderr, "".as_bytes());
1578            }
1579        }
1580    }
1581
1582    mod ExitStatus {
1583        #![allow(non_snake_case)]
1584
1585        mod display_fmt {
1586            use crate::{ExitStatus, OpaqueOsExitStatus};
1587
1588            #[test]
1589            fn format_exit_status_as_hex() {
1590                let exit_status = ExitStatus::from(0x7Fi32);
1591                assert_eq!(&format!("{}", exit_status), "0x7F");
1592            }
1593
1594            #[test]
1595            fn format_negative_exit_status_as_hex() {
1596                let exit_status = ExitStatus::Code(-1i32 as u32 as _);
1597                assert_eq!(&format!("{}", exit_status), "0xFFFFFFFF");
1598            }
1599
1600            #[test]
1601            #[cfg(unix)]
1602            fn display_for_non_exit_code_on_unix() {
1603                let signal = OpaqueOsExitStatus::from_signal_number(9);
1604                assert_eq!(&format!("{}", signal), "signal(9)");
1605            }
1606        }
1607
1608        mod new {
1609            use crate::ExitStatus;
1610
1611            #[test]
1612            fn can_be_create_from_many_numbers() {
1613                let status = ExitStatus::from(12u8);
1614                assert_eq!(status, ExitStatus::Code(12));
1615                let status = ExitStatus::from(-12i8);
1616                assert_eq!(status, ExitStatus::Code(-12));
1617                let status = ExitStatus::from(12u16);
1618                assert_eq!(status, ExitStatus::Code(12));
1619                let status = ExitStatus::from(-12i16);
1620                assert_eq!(status, ExitStatus::Code(-12));
1621                let status = ExitStatus::from(u32::MAX);
1622                assert_eq!(status, ExitStatus::Code(u32::MAX as i64));
1623                let status = ExitStatus::from(-1i32);
1624                assert_eq!(status, ExitStatus::Code(-1));
1625                let status = ExitStatus::from(-13i64);
1626                assert_eq!(status, ExitStatus::Code(-13));
1627            }
1628
1629            #[test]
1630            fn can_compare_to_many_numbers() {
1631                let status = ExitStatus::from(12u8);
1632                assert_eq!(status, 12u8);
1633                let status = ExitStatus::from(-12i8);
1634                assert_eq!(status, -12i8);
1635                let status = ExitStatus::from(12u16);
1636                assert_eq!(status, 12u16);
1637                let status = ExitStatus::from(-12i16);
1638                assert_eq!(status, -12i16);
1639                let status = ExitStatus::from(u32::MAX);
1640                assert_eq!(status, u32::MAX);
1641                let status = ExitStatus::from(-1i32);
1642                assert_eq!(status, -1i32);
1643                let status = ExitStatus::from(-13i64);
1644                assert_eq!(status, -13i64);
1645            }
1646        }
1647    }
1648
1649    #[cfg(unix)]
1650    mod signal_number {
1651        use proptest::prelude::*;
1652
1653        use crate::OpaqueOsExitStatus;
1654
1655        proptest! {
1656            #[test]
1657            fn from_to_signal_number(
1658                nr in any::<i32>()
1659            ) {
1660                let exit_status = OpaqueOsExitStatus::from_signal_number(nr);
1661                assert_eq!(exit_status.signal_number(), nr);
1662            }
1663
1664        }
1665    }
1666}