1use crate::{
2 Command, Control, Error, ErrorKind, Result, ResultExt as _, StandardError, StandardInput,
3 StandardOutput,
4};
5use futures::prelude::*;
6use tokio_process::{ChildStderr, ChildStdin, ChildStdout, CommandExt as _};
7
8pub struct Process {
16 command: Command,
17 process: tokio_process::Child,
18}
19
20impl Process {
21 pub fn spawn(command: Command) -> Result<Self> {
32 let mut cmd = command.process_command();
33 let process = cmd
34 .spawn_async()
35 .chain_err(|| ErrorKind::CannotSpawnCommand(command.clone()))?;
36 Ok(Process { command, process })
37 }
38
39 pub fn stdin(&mut self) -> &mut Option<ChildStdin> {
40 self.process.stdin()
41 }
42
43 pub fn stdout(&mut self) -> &mut Option<ChildStdout> {
44 self.process.stdout()
45 }
46
47 pub fn stderr(&mut self) -> &mut Option<ChildStderr> {
48 self.process.stderr()
49 }
50}
51
52impl Control for Process {
53 #[inline]
54 fn command(&self) -> &Command {
55 &self.command
56 }
57
58 #[inline]
60 fn id(&self) -> u32 {
61 self.process.id()
62 }
63
64 #[inline]
68 fn kill(&mut self) -> Result<()> {
69 self.process
70 .kill()
71 .chain_err(|| ErrorKind::CannotKillProcess(self.command().clone(), self.id()))
72 }
73}
74
75impl<'a> StandardInput<'a> for Process {
76 #[inline]
77 fn standard_input(&mut self) -> &mut ChildStdin {
78 match self.process.stdin() {
79 None => unreachable!(),
80 Some(stdin) => stdin,
81 }
82 }
83}
84
85impl<'a> StandardOutput<'a> for Process {
86 #[inline]
87 fn standard_output(&mut self) -> &mut ChildStdout {
88 match self.process.stdout() {
89 None => unreachable!(),
90 Some(stdout) => stdout,
91 }
92 }
93}
94
95impl<'a> StandardError<'a> for Process {
96 #[inline]
97 fn standard_error(&mut self) -> &mut ChildStderr {
98 match self.process.stderr() {
99 None => unreachable!(),
100 Some(stderr) => stderr,
101 }
102 }
103}
104
105impl Future for Process {
106 type Item = <tokio_process::Child as Future>::Item;
107 type Error = Error;
108
109 #[inline]
110 fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
111 self.process
112 .poll()
113 .chain_err(|| ErrorKind::Poll(self.command.clone()))
114 }
115}
116
117#[cfg(test)]
118mod test {
119 use super::*;
120 use crate::Program;
121 use tokio_codec::LinesCodec;
122
123 #[test]
124 fn echo_stdout() -> Result<()> {
125 let mut cmd = Command::new(Program::new("rustc".to_owned())?);
126 cmd.arguments(&["--version"]);
127
128 let mut captured = Process::spawn(cmd)?
129 .capture_stdout(LinesCodec::new())
130 .wait();
131
132 let rustc_version: String = captured.next().unwrap()?;
133
134 assert!(rustc_version.starts_with("rustc"));
135
136 Ok(())
137 }
138
139 #[test]
140 fn cat_stdin_stderr() -> Result<()> {
141 let mut cmd = Command::new(Program::new("rustc".to_owned())?);
142 cmd.arguments(&["file-that-does-not-exist"]);
143
144 let mut captured = Process::spawn(cmd)?
145 .capture_stderr(LinesCodec::new())
146 .wait();
147
148 assert_eq!(
149 captured.next().unwrap()?,
150 "error: couldn\'t read file-that-does-not-exist: No such file or directory (os error 2)",
151 );
152
153 Ok(())
154 }
155
156 fn send_and_check<P, I>(process: P, item: I) -> Result<P>
157 where
158 P: Stream<Item = I, Error = Error> + Sink<SinkItem = I, SinkError = Error>,
159 I: std::fmt::Debug + Clone + PartialEq + Eq,
160 {
161 let process = process.send(item.clone()).wait()?;
162 let mut captured = Stream::wait(process);
163
164 assert_eq!(captured.next().unwrap()?, item);
165
166 Ok(captured.into_inner())
167 }
168
169 #[cfg(windows)]
170 #[test]
171 fn windows__stdin_stdout() -> Result<()> {
172 Ok(())
175 }
176
177 #[cfg(unix)]
178 #[test]
179 fn unix_cat_stdin_stdout() -> Result<()> {
180 let cmd = Command::new(Program::new("cat".to_owned())?);
181
182 let process = Process::spawn(cmd)?
183 .capture_stdout(LinesCodec::new())
184 .send_stdin(LinesCodec::new());
185
186 let process = send_and_check(process, "Hello World!".to_owned())?;
187 let _process = send_and_check(process, "Bawawa".to_owned())?;
188
189 Ok(())
190 }
191}