/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ pub use std::process::*; use crate::std::mock::{mock_key, MockKey}; use std::ffi::{OsStr, OsString}; use std::io::Result; use std::sync::{Arc, Mutex}; mock_key! { // Uses PathBuf rather than OsString to avoid path separator differences. pub struct MockCommand(::std::path::PathBuf) => Box Result + Send + Sync> } #[derive(Debug)] pub struct Command { pub program: OsString, pub args: Vec, pub env: std::collections::HashMap, pub stdin: Vec, // XXX The spawn stuff is hacky, but for now there's only one case where we really need to // interact with `spawn` so we live with it for testing. pub spawning: bool, pub spawned_child: Mutex>, } impl Command { pub fn mock>(program: S) -> MockCommand { MockCommand(program.as_ref().into()) } pub fn new>(program: S) -> Self { Command { program: program.as_ref().into(), args: vec![], env: Default::default(), stdin: Default::default(), spawning: false, spawned_child: Mutex::new(None), } } pub fn arg>(&mut self, arg: S) -> &mut Self { self.args.push(arg.as_ref().into()); self } pub fn args(&mut self, args: I) -> &mut Self where I: IntoIterator, S: AsRef, { for arg in args.into_iter() { self.arg(arg); } self } pub fn env(&mut self, key: K, val: V) -> &mut Self where K: AsRef, V: AsRef, { self.env.insert(key.as_ref().into(), val.as_ref().into()); self } pub fn stdin>(&mut self, _cfg: T) -> &mut Self { self } pub fn stdout>(&mut self, _cfg: T) -> &mut Self { self } pub fn stderr>(&mut self, _cfg: T) -> &mut Self { self } pub fn output(&mut self) -> std::io::Result { MockCommand(self.program.as_os_str().into()).get(|f| f(self)) } pub fn spawn(&mut self) -> std::io::Result { self.spawning = true; self.output()?; self.spawning = false; let stdin = Arc::new(Mutex::new(vec![])); Ok(Child { stdin: Some(ChildStdin { data: stdin.clone(), }), cmd: self.clone_for_child(), stdin_data: Some(stdin), }) } #[cfg(windows)] pub fn creation_flags(&mut self, _flags: u32) -> &mut Self { self } pub fn output_from_real_command(&self) -> std::io::Result { let mut spawned_child = self.spawned_child.lock().unwrap(); if spawned_child.is_none() { *spawned_child = Some( ::std::process::Command::new(self.program.clone()) .args(self.args.clone()) .envs(self.env.clone()) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn()?, ); } if self.spawning { return Ok(success_output()); } let mut child = spawned_child.take().unwrap(); { let mut input = child.stdin.take().unwrap(); std::io::copy(&mut std::io::Cursor::new(&self.stdin), &mut input)?; } child.wait_with_output() } fn clone_for_child(&self) -> Self { Command { program: self.program.clone(), args: self.args.clone(), env: self.env.clone(), stdin: self.stdin.clone(), spawning: false, spawned_child: Mutex::new(self.spawned_child.lock().unwrap().take()), } } } pub struct Child { pub stdin: Option, cmd: Command, stdin_data: Option>>>, } impl Child { pub fn wait_with_output(mut self) -> std::io::Result { self.ref_wait_with_output().unwrap() } fn ref_wait_with_output(&mut self) -> Option> { drop(self.stdin.take()); if let Some(stdin) = self.stdin_data.take() { self.cmd.stdin = Arc::try_unwrap(stdin) .expect("stdin not dropped, wait_with_output may block") .into_inner() .unwrap(); Some(MockCommand(self.cmd.program.as_os_str().into()).get(|f| f(&self.cmd))) } else { None } } } pub struct ChildStdin { data: Arc>>, } impl std::io::Write for ChildStdin { fn write(&mut self, buf: &[u8]) -> Result { self.data.lock().unwrap().write(buf) } fn flush(&mut self) -> Result<()> { Ok(()) } } #[cfg(unix)] pub fn success_exit_status() -> ExitStatus { use std::os::unix::process::ExitStatusExt; ExitStatus::from_raw(0) } #[cfg(windows)] pub fn success_exit_status() -> ExitStatus { use std::os::windows::process::ExitStatusExt; ExitStatus::from_raw(0) } pub fn success_output() -> Output { Output { status: success_exit_status(), stdout: vec![], stderr: vec![], } }