1 // Copyright 2016 Mozilla Foundation
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! Traits and types for mocking process execution.
16 //!
17 //! This module provides a set of traits and types that can be used
18 //! to write code that expects to execute processes using `std::process::Command`
19 //! in a way that can be mocked for tests.
20 //!
21 //! Instead of using `Command::new()`, make your code generic using
22 //! `CommandCreator` as a trait bound, and use its `new_command` method.
23 //! `new_command` returns an object implementing `CommandChild`, which
24 //! mirrors the methods of `Command`.
25 //!
26 //! For production use, you can then instantiate your code with
27 //! `ProcessCommandCreator`, which simply returns `Command::new()` from
28 //! its `new_command` method.
29 //!
30 //! For testing, you can instantiate your code with `MockCommandCreator`,
31 //! which creates `MockCommand` objects which in turn spawn `MockChild`
32 //! objects. You can use `MockCommand::next_command_spawns` to provide
33 //! the result of `spawn` from the next `MockCommand` that it creates.
34 //! `MockCommandCreator::new_command` will fail an `assert` if it attempts
35 //! to create a command and does not have any pending `MockChild` objects
36 //! to hand out, so your tests must provide enough outputs for all
37 //! expected process executions in the test.
38 //!
39 //! If your code under test needs to spawn processes across threads, you
40 //! can use `CommandCreatorSync` as a trait bound, which is implemented for
41 //! `ProcessCommandCreator` (since it has no state), and also for
42 //! `Arc<Mutex<CommandCreator>>`. `CommandCreatorSync` provides a
43 //! `new_command_sync` method which your code can call to create new
44 //! objects implementing `CommandChild` in a thread-safe way. Your tests can
45 //! then create an `Arc<Mutex<MockCommandCreator>>` and safely provide
46 //! `MockChild` outputs.
47 
48 use crate::errors::*;
49 use crate::jobserver::{Acquired, Client};
50 use futures::future::{self, Future};
51 use std::boxed::Box;
52 use std::ffi::{OsStr, OsString};
53 use std::fmt;
54 use std::io;
55 use std::path::Path;
56 use std::process::{Command, ExitStatus, Output, Stdio};
57 use std::sync::{Arc, Mutex};
58 use tokio_io::{AsyncRead, AsyncWrite};
59 use tokio_process::{self, ChildStderr, ChildStdin, ChildStdout, CommandExt};
60 
61 /// A trait that provides a subset of the methods of `std::process::Child`.
62 pub trait CommandChild {
63     /// The type of the process' standard input.
64     type I: AsyncWrite + Sync + Send + 'static;
65     /// The type of the process' standard output.
66     type O: AsyncRead + Sync + Send + 'static;
67     /// The type of the process' standard error.
68     type E: AsyncRead + Sync + Send + 'static;
69 
70     /// Take the stdin object from the process, if available.
take_stdin(&mut self) -> Option<Self::I>71     fn take_stdin(&mut self) -> Option<Self::I>;
72     /// Take the stdout object from the process, if available.
take_stdout(&mut self) -> Option<Self::O>73     fn take_stdout(&mut self) -> Option<Self::O>;
74     /// Take the stderr object from the process, if available.
take_stderr(&mut self) -> Option<Self::E>75     fn take_stderr(&mut self) -> Option<Self::E>;
76     /// Wait for the process to complete and return its exit status.
wait(self) -> Box<dyn Future<Item = ExitStatus, Error = io::Error>>77     fn wait(self) -> Box<dyn Future<Item = ExitStatus, Error = io::Error>>;
78     /// Wait for the process to complete and return its output.
wait_with_output(self) -> Box<dyn Future<Item = Output, Error = io::Error>>79     fn wait_with_output(self) -> Box<dyn Future<Item = Output, Error = io::Error>>;
80 }
81 
82 /// A trait that provides a subset of the methods of `std::process::Command`.
83 pub trait RunCommand: fmt::Debug {
84     /// The type returned by `spawn`.
85     type C: CommandChild + 'static;
86 
87     /// Append `arg` to the process commandline.
arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self88     fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self;
89     /// Append `args` to the process commandline.
args<S: AsRef<OsStr>>(&mut self, args: &[S]) -> &mut Self90     fn args<S: AsRef<OsStr>>(&mut self, args: &[S]) -> &mut Self;
91     /// Insert or update an environment variable mapping.
env<K, V>(&mut self, key: K, val: V) -> &mut Self where K: AsRef<OsStr>, V: AsRef<OsStr>92     fn env<K, V>(&mut self, key: K, val: V) -> &mut Self
93     where
94         K: AsRef<OsStr>,
95         V: AsRef<OsStr>;
96     /// Add or update multiple environment variable mappings.
envs<I, K, V>(&mut self, vars: I) -> &mut Self where I: IntoIterator<Item = (K, V)>, K: AsRef<OsStr>, V: AsRef<OsStr>97     fn envs<I, K, V>(&mut self, vars: I) -> &mut Self
98     where
99         I: IntoIterator<Item = (K, V)>,
100         K: AsRef<OsStr>,
101         V: AsRef<OsStr>;
102     /// Clears the entire environment map for the child process.
env_clear(&mut self) -> &mut Self103     fn env_clear(&mut self) -> &mut Self;
104     /// Set the working directory of the process to `dir`.
current_dir<P: AsRef<Path>>(&mut self, dir: P) -> &mut Self105     fn current_dir<P: AsRef<Path>>(&mut self, dir: P) -> &mut Self;
106     /// Create the proces without a visible console on Windows.
no_console(&mut self) -> &mut Self107     fn no_console(&mut self) -> &mut Self;
108     /// Set the process' stdin from `cfg`.
stdin(&mut self, cfg: Stdio) -> &mut Self109     fn stdin(&mut self, cfg: Stdio) -> &mut Self;
110     /// Set the process' stdout from `cfg`.
stdout(&mut self, cfg: Stdio) -> &mut Self111     fn stdout(&mut self, cfg: Stdio) -> &mut Self;
112     /// Set the process' stderr from `cfg`.
stderr(&mut self, cfg: Stdio) -> &mut Self113     fn stderr(&mut self, cfg: Stdio) -> &mut Self;
114     /// Execute the process and return a process object.
spawn(&mut self) -> SFuture<Self::C>115     fn spawn(&mut self) -> SFuture<Self::C>;
116 }
117 
118 /// A trait that provides a means to create objects implementing `RunCommand`.
119 ///
120 /// This is provided so that `MockCommandCreator` can have state for testing.
121 /// For the non-testing scenario, `ProcessCommandCreator` is simply a unit
122 /// struct with a trivial implementation of this.
123 pub trait CommandCreator {
124     /// The type returned by `new_command`.
125     type Cmd: RunCommand;
126 
127     /// Create a new instance of this type.
new(client: &Client) -> Self128     fn new(client: &Client) -> Self;
129     /// Create a new object that implements `RunCommand` that can be used
130     /// to create a new process.
new_command<S: AsRef<OsStr>>(&mut self, program: S) -> Self::Cmd131     fn new_command<S: AsRef<OsStr>>(&mut self, program: S) -> Self::Cmd;
132 }
133 
134 /// A trait for simplifying the normal case while still allowing the mock case requiring mutability.
135 pub trait CommandCreatorSync: Clone + 'static {
136     type Cmd: RunCommand;
137 
new(client: &Client) -> Self138     fn new(client: &Client) -> Self;
139 
new_command_sync<S: AsRef<OsStr>>(&mut self, program: S) -> Self::Cmd140     fn new_command_sync<S: AsRef<OsStr>>(&mut self, program: S) -> Self::Cmd;
141 }
142 
143 pub struct Child {
144     inner: tokio_process::Child,
145     token: Acquired,
146 }
147 
148 /// Trivial implementation of `CommandChild` for `std::process::Child`.
149 impl CommandChild for Child {
150     type I = ChildStdin;
151     type O = ChildStdout;
152     type E = ChildStderr;
153 
take_stdin(&mut self) -> Option<ChildStdin>154     fn take_stdin(&mut self) -> Option<ChildStdin> {
155         self.inner.stdin().take()
156     }
take_stdout(&mut self) -> Option<ChildStdout>157     fn take_stdout(&mut self) -> Option<ChildStdout> {
158         self.inner.stdout().take()
159     }
take_stderr(&mut self) -> Option<ChildStderr>160     fn take_stderr(&mut self) -> Option<ChildStderr> {
161         self.inner.stderr().take()
162     }
163 
wait(self) -> Box<dyn Future<Item = ExitStatus, Error = io::Error>>164     fn wait(self) -> Box<dyn Future<Item = ExitStatus, Error = io::Error>> {
165         let Child { inner, token } = self;
166         Box::new(inner.map(|ret| {
167             drop(token);
168             ret
169         }))
170     }
171 
wait_with_output(self) -> Box<dyn Future<Item = Output, Error = io::Error>>172     fn wait_with_output(self) -> Box<dyn Future<Item = Output, Error = io::Error>> {
173         let Child { inner, token } = self;
174         Box::new(inner.wait_with_output().map(|ret| {
175             drop(token);
176             ret
177         }))
178     }
179 }
180 
181 pub struct AsyncCommand {
182     inner: Option<Command>,
183     jobserver: Client,
184 }
185 
186 impl AsyncCommand {
new<S: AsRef<OsStr>>(program: S, jobserver: Client) -> AsyncCommand187     pub fn new<S: AsRef<OsStr>>(program: S, jobserver: Client) -> AsyncCommand {
188         AsyncCommand {
189             inner: Some(Command::new(program)),
190             jobserver,
191         }
192     }
193 
inner(&mut self) -> &mut Command194     fn inner(&mut self) -> &mut Command {
195         self.inner.as_mut().expect("can't reuse commands")
196     }
197 }
198 
199 /// Trivial implementation of `RunCommand` for `std::process::Command`.
200 impl RunCommand for AsyncCommand {
201     type C = Child;
202 
arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut AsyncCommand203     fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut AsyncCommand {
204         self.inner().arg(arg);
205         self
206     }
args<S: AsRef<OsStr>>(&mut self, args: &[S]) -> &mut AsyncCommand207     fn args<S: AsRef<OsStr>>(&mut self, args: &[S]) -> &mut AsyncCommand {
208         self.inner().args(args);
209         self
210     }
env<K, V>(&mut self, key: K, val: V) -> &mut AsyncCommand where K: AsRef<OsStr>, V: AsRef<OsStr>,211     fn env<K, V>(&mut self, key: K, val: V) -> &mut AsyncCommand
212     where
213         K: AsRef<OsStr>,
214         V: AsRef<OsStr>,
215     {
216         self.inner().env(key, val);
217         self
218     }
envs<I, K, V>(&mut self, vars: I) -> &mut Self where I: IntoIterator<Item = (K, V)>, K: AsRef<OsStr>, V: AsRef<OsStr>,219     fn envs<I, K, V>(&mut self, vars: I) -> &mut Self
220     where
221         I: IntoIterator<Item = (K, V)>,
222         K: AsRef<OsStr>,
223         V: AsRef<OsStr>,
224     {
225         self.inner().envs(vars);
226         self
227     }
env_clear(&mut self) -> &mut AsyncCommand228     fn env_clear(&mut self) -> &mut AsyncCommand {
229         self.inner().env_clear();
230         self
231     }
current_dir<P: AsRef<Path>>(&mut self, dir: P) -> &mut AsyncCommand232     fn current_dir<P: AsRef<Path>>(&mut self, dir: P) -> &mut AsyncCommand {
233         self.inner().current_dir(dir);
234         self
235     }
236 
237     #[cfg(windows)]
no_console(&mut self) -> &mut AsyncCommand238     fn no_console(&mut self) -> &mut AsyncCommand {
239         use std::os::windows::process::CommandExt;
240         const CREATE_NO_WINDOW: u32 = 0x08000000;
241         self.inner().creation_flags(CREATE_NO_WINDOW);
242         self
243     }
244 
245     #[cfg(unix)]
no_console(&mut self) -> &mut AsyncCommand246     fn no_console(&mut self) -> &mut AsyncCommand {
247         self
248     }
249 
stdin(&mut self, cfg: Stdio) -> &mut AsyncCommand250     fn stdin(&mut self, cfg: Stdio) -> &mut AsyncCommand {
251         self.inner().stdin(cfg);
252         self
253     }
stdout(&mut self, cfg: Stdio) -> &mut AsyncCommand254     fn stdout(&mut self, cfg: Stdio) -> &mut AsyncCommand {
255         self.inner().stdout(cfg);
256         self
257     }
stderr(&mut self, cfg: Stdio) -> &mut AsyncCommand258     fn stderr(&mut self, cfg: Stdio) -> &mut AsyncCommand {
259         self.inner().stderr(cfg);
260         self
261     }
spawn(&mut self) -> SFuture<Child>262     fn spawn(&mut self) -> SFuture<Child> {
263         let mut inner = self.inner.take().unwrap();
264         inner.env_remove("MAKEFLAGS");
265         inner.env_remove("MFLAGS");
266         inner.env_remove("CARGO_MAKEFLAGS");
267         self.jobserver.configure(&mut inner);
268         Box::new(self.jobserver.acquire().and_then(move |token| {
269             let child = inner
270                 .spawn_async()
271                 .with_context(|| format!("failed to spawn {:?}", inner))?;
272             Ok(Child {
273                 inner: child,
274                 token,
275             })
276         }))
277     }
278 }
279 
280 impl fmt::Debug for AsyncCommand {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result281     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
282         self.inner.fmt(f)
283     }
284 }
285 
286 /// Struct to use `RunCommand` with `std::process::Command`.
287 #[derive(Clone)]
288 pub struct ProcessCommandCreator {
289     jobserver: Client,
290 }
291 
292 /// Trivial implementation of `CommandCreator` for `ProcessCommandCreator`.
293 impl CommandCreator for ProcessCommandCreator {
294     type Cmd = AsyncCommand;
295 
new(client: &Client) -> ProcessCommandCreator296     fn new(client: &Client) -> ProcessCommandCreator {
297         ProcessCommandCreator {
298             jobserver: client.clone(),
299         }
300     }
301 
new_command<S: AsRef<OsStr>>(&mut self, program: S) -> AsyncCommand302     fn new_command<S: AsRef<OsStr>>(&mut self, program: S) -> AsyncCommand {
303         AsyncCommand::new(program, self.jobserver.clone())
304     }
305 }
306 
307 /// Trivial implementation of `CommandCreatorSync` for `ProcessCommandCreator`.
308 impl CommandCreatorSync for ProcessCommandCreator {
309     type Cmd = AsyncCommand;
310 
new(client: &Client) -> ProcessCommandCreator311     fn new(client: &Client) -> ProcessCommandCreator {
312         CommandCreator::new(client)
313     }
314 
new_command_sync<S: AsRef<OsStr>>(&mut self, program: S) -> AsyncCommand315     fn new_command_sync<S: AsRef<OsStr>>(&mut self, program: S) -> AsyncCommand {
316         // This doesn't actually use any mutable state.
317         self.new_command(program)
318     }
319 }
320 
321 #[cfg(unix)]
322 use std::os::unix::process::ExitStatusExt;
323 #[cfg(windows)]
324 use std::os::windows::process::ExitStatusExt;
325 
326 #[cfg(unix)]
327 pub type ExitStatusValue = i32;
328 #[cfg(windows)]
329 pub type ExitStatusValue = u32;
330 
331 #[allow(dead_code)]
exit_status(v: ExitStatusValue) -> ExitStatus332 pub fn exit_status(v: ExitStatusValue) -> ExitStatus {
333     ExitStatus::from_raw(v)
334 }
335 
336 /// A struct that mocks `std::process::Child`.
337 #[allow(dead_code)]
338 #[derive(Debug)]
339 pub struct MockChild {
340     //TODO: this doesn't work to actually track writes...
341     /// A `Cursor` to hand out as stdin.
342     pub stdin: Option<io::Cursor<Vec<u8>>>,
343     /// A `Cursor` to hand out as stdout.
344     pub stdout: Option<io::Cursor<Vec<u8>>>,
345     /// A `Cursor` to hand out as stderr.
346     pub stderr: Option<io::Cursor<Vec<u8>>>,
347     /// The `Result` to be handed out when `wait` is called.
348     pub wait_result: Option<io::Result<ExitStatus>>,
349 }
350 
351 /// A mocked child process that simply returns stored values for its status and output.
352 impl MockChild {
353     /// Create a `MockChild` that will return the specified `status`, `stdout`, and `stderr` when waited upon.
354     #[allow(dead_code)]
new<T: AsRef<[u8]>, U: AsRef<[u8]>>( status: ExitStatus, stdout: T, stderr: U, ) -> MockChild355     pub fn new<T: AsRef<[u8]>, U: AsRef<[u8]>>(
356         status: ExitStatus,
357         stdout: T,
358         stderr: U,
359     ) -> MockChild {
360         MockChild {
361             stdin: Some(io::Cursor::new(vec![])),
362             stdout: Some(io::Cursor::new(stdout.as_ref().to_vec())),
363             stderr: Some(io::Cursor::new(stderr.as_ref().to_vec())),
364             wait_result: Some(Ok(status)),
365         }
366     }
367 
368     /// Create a `MockChild` that will return the specified `err` when waited upon.
369     #[allow(dead_code)]
with_error(err: io::Error) -> MockChild370     pub fn with_error(err: io::Error) -> MockChild {
371         MockChild {
372             stdin: None,
373             stdout: None,
374             stderr: None,
375             wait_result: Some(Err(err)),
376         }
377     }
378 }
379 
380 impl CommandChild for MockChild {
381     type I = io::Cursor<Vec<u8>>;
382     type O = io::Cursor<Vec<u8>>;
383     type E = io::Cursor<Vec<u8>>;
384 
take_stdin(&mut self) -> Option<io::Cursor<Vec<u8>>>385     fn take_stdin(&mut self) -> Option<io::Cursor<Vec<u8>>> {
386         self.stdin.take()
387     }
take_stdout(&mut self) -> Option<io::Cursor<Vec<u8>>>388     fn take_stdout(&mut self) -> Option<io::Cursor<Vec<u8>>> {
389         self.stdout.take()
390     }
take_stderr(&mut self) -> Option<io::Cursor<Vec<u8>>>391     fn take_stderr(&mut self) -> Option<io::Cursor<Vec<u8>>> {
392         self.stderr.take()
393     }
394 
wait(mut self) -> Box<dyn Future<Item = ExitStatus, Error = io::Error>>395     fn wait(mut self) -> Box<dyn Future<Item = ExitStatus, Error = io::Error>> {
396         Box::new(future::result(self.wait_result.take().unwrap()))
397     }
398 
wait_with_output(self) -> Box<dyn Future<Item = Output, Error = io::Error>>399     fn wait_with_output(self) -> Box<dyn Future<Item = Output, Error = io::Error>> {
400         let MockChild {
401             stdout,
402             stderr,
403             wait_result,
404             ..
405         } = self;
406         let result = wait_result.unwrap().map(|status| Output {
407             status,
408             stdout: stdout.map(|c| c.into_inner()).unwrap_or_else(Vec::new),
409             stderr: stderr.map(|c| c.into_inner()).unwrap_or_else(Vec::new),
410         });
411         Box::new(future::result(result))
412     }
413 }
414 
415 pub enum ChildOrCall {
416     Child(Result<MockChild>),
417     Call(Box<dyn Fn(&[OsString]) -> Result<MockChild> + Send>),
418 }
419 
420 impl fmt::Debug for ChildOrCall {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result421     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
422         match *self {
423             ChildOrCall::Child(ref r) => write!(f, "ChildOrCall::Child({:?}", r),
424             ChildOrCall::Call(_) => write!(f, "ChildOrCall::Call(...)"),
425         }
426     }
427 }
428 
429 /// A mocked command that simply returns its `child` from `spawn`.
430 #[allow(dead_code)]
431 #[derive(Debug)]
432 pub struct MockCommand {
433     pub child: Option<ChildOrCall>,
434     pub args: Vec<OsString>,
435 }
436 
437 impl RunCommand for MockCommand {
438     type C = MockChild;
439 
arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut MockCommand440     fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut MockCommand {
441         self.args.push(arg.as_ref().to_owned());
442         self
443     }
args<S: AsRef<OsStr>>(&mut self, args: &[S]) -> &mut MockCommand444     fn args<S: AsRef<OsStr>>(&mut self, args: &[S]) -> &mut MockCommand {
445         self.args.extend(args.iter().map(|a| a.as_ref().to_owned()));
446         self
447     }
env<K, V>(&mut self, _key: K, _val: V) -> &mut MockCommand where K: AsRef<OsStr>, V: AsRef<OsStr>,448     fn env<K, V>(&mut self, _key: K, _val: V) -> &mut MockCommand
449     where
450         K: AsRef<OsStr>,
451         V: AsRef<OsStr>,
452     {
453         self
454     }
envs<I, K, V>(&mut self, _vars: I) -> &mut Self where I: IntoIterator<Item = (K, V)>, K: AsRef<OsStr>, V: AsRef<OsStr>,455     fn envs<I, K, V>(&mut self, _vars: I) -> &mut Self
456     where
457         I: IntoIterator<Item = (K, V)>,
458         K: AsRef<OsStr>,
459         V: AsRef<OsStr>,
460     {
461         self
462     }
env_clear(&mut self) -> &mut MockCommand463     fn env_clear(&mut self) -> &mut MockCommand {
464         self
465     }
current_dir<P: AsRef<Path>>(&mut self, _dir: P) -> &mut MockCommand466     fn current_dir<P: AsRef<Path>>(&mut self, _dir: P) -> &mut MockCommand {
467         //TODO: assert value of dir
468         self
469     }
no_console(&mut self) -> &mut MockCommand470     fn no_console(&mut self) -> &mut MockCommand {
471         self
472     }
stdin(&mut self, _cfg: Stdio) -> &mut MockCommand473     fn stdin(&mut self, _cfg: Stdio) -> &mut MockCommand {
474         self
475     }
stdout(&mut self, _cfg: Stdio) -> &mut MockCommand476     fn stdout(&mut self, _cfg: Stdio) -> &mut MockCommand {
477         self
478     }
stderr(&mut self, _cfg: Stdio) -> &mut MockCommand479     fn stderr(&mut self, _cfg: Stdio) -> &mut MockCommand {
480         self
481     }
spawn(&mut self) -> SFuture<MockChild>482     fn spawn(&mut self) -> SFuture<MockChild> {
483         match self.child.take().unwrap() {
484             ChildOrCall::Child(c) => Box::new(future::result(c)),
485             ChildOrCall::Call(f) => Box::new(future::result(f(&self.args))),
486         }
487     }
488 }
489 
490 /// `MockCommandCreator` allows mocking out process creation by providing `MockChild` instances to be used in advance.
491 #[allow(dead_code)]
492 pub struct MockCommandCreator {
493     /// Data to be used as the return value of `MockCommand::spawn`.
494     pub children: Vec<ChildOrCall>,
495 }
496 
497 impl MockCommandCreator {
498     /// The next `MockCommand` created will return `child` from `RunCommand::spawn`.
499     #[allow(dead_code)]
next_command_spawns(&mut self, child: Result<MockChild>)500     pub fn next_command_spawns(&mut self, child: Result<MockChild>) {
501         self.children.push(ChildOrCall::Child(child));
502     }
503 
504     /// The next `MockCommand` created will call `call` with the command-line
505     /// arguments passed to the command.
506     #[allow(dead_code)]
next_command_calls<C>(&mut self, call: C) where C: Fn(&[OsString]) -> Result<MockChild> + Send + 'static,507     pub fn next_command_calls<C>(&mut self, call: C)
508     where
509         C: Fn(&[OsString]) -> Result<MockChild> + Send + 'static,
510     {
511         self.children.push(ChildOrCall::Call(Box::new(call)));
512     }
513 }
514 
515 impl CommandCreator for MockCommandCreator {
516     type Cmd = MockCommand;
517 
new(_client: &Client) -> MockCommandCreator518     fn new(_client: &Client) -> MockCommandCreator {
519         MockCommandCreator {
520             children: Vec::new(),
521         }
522     }
523 
new_command<S: AsRef<OsStr>>(&mut self, _program: S) -> MockCommand524     fn new_command<S: AsRef<OsStr>>(&mut self, _program: S) -> MockCommand {
525         assert!(!self.children.is_empty(), "Too many calls to MockCommandCreator::new_command, or not enough to MockCommandCreator::new_command_spawns!");
526         //TODO: assert value of program
527         MockCommand {
528             child: Some(self.children.remove(0)),
529             args: vec![],
530         }
531     }
532 }
533 
534 /// To simplify life for using a `CommandCreator` across multiple threads.
535 impl<T: CommandCreator + 'static> CommandCreatorSync for Arc<Mutex<T>> {
536     type Cmd = T::Cmd;
537 
new(client: &Client) -> Arc<Mutex<T>>538     fn new(client: &Client) -> Arc<Mutex<T>> {
539         Arc::new(Mutex::new(T::new(client)))
540     }
541 
new_command_sync<S: AsRef<OsStr>>(&mut self, program: S) -> T::Cmd542     fn new_command_sync<S: AsRef<OsStr>>(&mut self, program: S) -> T::Cmd {
543         self.lock().unwrap().new_command(program)
544     }
545 }
546 
547 #[cfg(test)]
548 mod test {
549     use super::*;
550     use crate::jobserver::Client;
551     use crate::test::utils::*;
552     use futures::Future;
553     use std::ffi::OsStr;
554     use std::io;
555     use std::process::{ExitStatus, Output};
556     use std::sync::{Arc, Mutex};
557     use std::thread;
558 
spawn_command<T: CommandCreator, S: AsRef<OsStr>>( creator: &mut T, program: S, ) -> Result<<<T as CommandCreator>::Cmd as RunCommand>::C>559     fn spawn_command<T: CommandCreator, S: AsRef<OsStr>>(
560         creator: &mut T,
561         program: S,
562     ) -> Result<<<T as CommandCreator>::Cmd as RunCommand>::C> {
563         creator.new_command(program).spawn().wait()
564     }
565 
spawn_wait_command<T: CommandCreator, S: AsRef<OsStr>>( creator: &mut T, program: S, ) -> Result<ExitStatus>566     fn spawn_wait_command<T: CommandCreator, S: AsRef<OsStr>>(
567         creator: &mut T,
568         program: S,
569     ) -> Result<ExitStatus> {
570         Ok(spawn_command(creator, program)?.wait().wait()?)
571     }
572 
spawn_output_command<T: CommandCreator, S: AsRef<OsStr>>( creator: &mut T, program: S, ) -> Result<Output>573     fn spawn_output_command<T: CommandCreator, S: AsRef<OsStr>>(
574         creator: &mut T,
575         program: S,
576     ) -> Result<Output> {
577         Ok(spawn_command(creator, program)?.wait_with_output().wait()?)
578     }
579 
spawn_on_thread<T: CommandCreatorSync + Send + 'static>( mut t: T, really: bool, ) -> ExitStatus580     fn spawn_on_thread<T: CommandCreatorSync + Send + 'static>(
581         mut t: T,
582         really: bool,
583     ) -> ExitStatus {
584         thread::spawn(move || {
585             if really {
586                 t.new_command_sync("foo")
587                     .spawn()
588                     .wait()
589                     .unwrap()
590                     .wait()
591                     .wait()
592                     .unwrap()
593             } else {
594                 exit_status(1)
595             }
596         })
597         .join()
598         .unwrap()
599     }
600 
601     #[test]
test_mock_command_wait()602     fn test_mock_command_wait() {
603         let client = Client::new_num(1);
604         let mut creator = MockCommandCreator::new(&client);
605         creator.next_command_spawns(Ok(MockChild::new(exit_status(0), "hello", "error")));
606         assert_eq!(
607             0,
608             spawn_wait_command(&mut creator, "foo")
609                 .unwrap()
610                 .code()
611                 .unwrap()
612         );
613     }
614 
615     #[test]
616     #[should_panic]
test_unexpected_new_command()617     fn test_unexpected_new_command() {
618         // If next_command_spawns hasn't been called enough times,
619         // new_command should panic.
620         let client = Client::new_num(1);
621         let mut creator = MockCommandCreator::new(&client);
622         creator.new_command("foo").spawn().wait().unwrap();
623     }
624 
625     #[test]
test_mock_command_output()626     fn test_mock_command_output() {
627         let client = Client::new_num(1);
628         let mut creator = MockCommandCreator::new(&client);
629         creator.next_command_spawns(Ok(MockChild::new(exit_status(0), "hello", "error")));
630         let output = spawn_output_command(&mut creator, "foo").unwrap();
631         assert_eq!(0, output.status.code().unwrap());
632         assert_eq!(b"hello".to_vec(), output.stdout);
633         assert_eq!(b"error".to_vec(), output.stderr);
634     }
635 
636     #[test]
test_mock_command_calls()637     fn test_mock_command_calls() {
638         let client = Client::new_num(1);
639         let mut creator = MockCommandCreator::new(&client);
640         creator.next_command_calls(|_| Ok(MockChild::new(exit_status(0), "hello", "error")));
641         let output = spawn_output_command(&mut creator, "foo").unwrap();
642         assert_eq!(0, output.status.code().unwrap());
643         assert_eq!(b"hello".to_vec(), output.stdout);
644         assert_eq!(b"error".to_vec(), output.stderr);
645     }
646 
647     #[test]
test_mock_spawn_error()648     fn test_mock_spawn_error() {
649         let client = Client::new_num(1);
650         let mut creator = MockCommandCreator::new(&client);
651         creator.next_command_spawns(Err(anyhow!("error")));
652         let e = spawn_command(&mut creator, "foo").err().unwrap();
653         assert_eq!("error", e.to_string());
654     }
655 
656     #[test]
test_mock_wait_error()657     fn test_mock_wait_error() {
658         let client = Client::new_num(1);
659         let mut creator = MockCommandCreator::new(&client);
660         creator.next_command_spawns(Ok(MockChild::with_error(io::Error::new(
661             io::ErrorKind::Other,
662             "error",
663         ))));
664         let e = spawn_wait_command(&mut creator, "foo").err().unwrap();
665         assert_eq!("error", e.to_string());
666     }
667 
668     #[test]
test_mock_command_sync()669     fn test_mock_command_sync() {
670         let client = Client::new_num(1);
671         let creator = Arc::new(Mutex::new(MockCommandCreator::new(&client)));
672         next_command(
673             &creator,
674             Ok(MockChild::new(exit_status(0), "hello", "error")),
675         );
676         assert_eq!(exit_status(0), spawn_on_thread(creator, true));
677     }
678 }
679