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