pwner/
tokio.rs

1#![allow(clippy::needless_doctest_main)]
2
3//! Holds the tokio implementation of an async process.
4//!
5//! All potentially blocking interactions are performed async, including the dropping
6//! of child processes (on *nix platforms).
7//!
8//! # Spawning an owned tokio process
9//!
10//! ```no_run
11//! use tokio::process::Command;
12//! use pwner::Spawner;
13//!
14//! Command::new("ls").spawn_owned().expect("ls command failed to start");
15//! ```
16//!
17//! # Reading from the process
18//!
19//! ```no_run
20//! # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
21//! use tokio::io::AsyncReadExt;
22//! use tokio::process::Command;
23//! use pwner::Spawner;
24//!
25//! let mut child = Command::new("ls").spawn_owned()?;
26//! let mut output = String::new();
27//! child.read_to_string(&mut output).await?;
28//! # Ok(())
29//! # }
30//! ```
31//!
32//! # Writing to the process
33//!
34//! ```no_run
35//! # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
36//! use tokio::io::{AsyncReadExt, AsyncWriteExt};
37//! use tokio::process::Command;
38//! use pwner::Spawner;
39//!
40//! let mut child = Command::new("cat").spawn_owned()?;
41//! child.write_all(b"hello\n").await?;
42//!
43//! let mut buffer = [0_u8; 10];
44//! child.read(&mut buffer).await?;
45//! # Ok(())
46//! # }
47//! ```
48//!
49//! # Graceful dropping
50//!
51//! **Note:** Only available on *nix platforms.
52//!
53//! When the owned process gets dropped, [`Process`](crate::Process) will try to kill it gracefully
54//! by sending a `SIGINT` and asynchronously wait for the process to die for 2 seconds. If the
55//! process still doesn't die, a `SIGTERM` is sent and another chance is given, until finally a
56//! `SIGKILL` is sent.
57//!
58//! ## Panics
59//!
60//! If the process is dropped without a tokio runtime, a panic will occur.
61//!
62//! ```should_panic
63//! use tokio::process::Command;
64//! use pwner::Spawner;
65//!
66//! {
67//!     let child = Command::new("ls").spawn_owned().expect("ls command failed to start");
68//! }
69//! ```
70//!
71//! Make sure that a runtime is available to kill the child process
72//!
73//! ```
74//! use tokio::process::Command;
75//! use pwner::Spawner;
76//!
77//! #[tokio::main(flavor = "current_thread")]
78//! async fn main() {
79//!     let child = Command::new("ls").spawn_owned().expect("ls command failed to start");
80//! }
81//! ```
82
83/// Possible sources to read from
84#[derive(Debug, Copy, Clone)]
85pub enum ReadSource {
86    /// Read from the child's stdout
87    Stdout,
88    /// Read from the child's stderr
89    Stderr,
90    /// Read from whichever has data available, the child's stdout or stderr
91    Both,
92}
93
94/// An implementation of [`Process`](crate::Process) that uses [`tokio::process`] as the launcher.
95///
96/// All read and write operations are async.
97///
98/// **Note:** On *nix platforms, the owned process will have 2 seconds between signals, which is
99/// run in a spawned task.
100///
101/// # Panics
102///
103/// When, on *nix platforms, a process gets dropped without a runtime.
104pub struct Duplex(Simplex, Output);
105
106impl super::Spawner for tokio::process::Command {
107    type Output = Duplex;
108
109    fn spawn_owned(&mut self) -> std::io::Result<Self::Output> {
110        let mut process = self
111            .stdin(std::process::Stdio::piped())
112            .stdout(std::process::Stdio::piped())
113            .stderr(std::process::Stdio::piped())
114            .spawn()?;
115
116        let stdin = process.stdin.take().unwrap();
117        let stdout = process.stdout.take().unwrap();
118        let stderr = process.stderr.take().unwrap();
119
120        Ok(Duplex(
121            Simplex(Some(ProcessImpl { process, stdin })),
122            Output {
123                read_source: ReadSource::Both,
124                stdout,
125                stderr,
126            },
127        ))
128    }
129}
130
131impl super::Process for Duplex {}
132
133impl Duplex {
134    /// Returns the OS-assigned process identifier associated with this child.
135    ///
136    /// # Examples
137    ///
138    /// Basic usage:
139    ///
140    /// ```no_run
141    /// use tokio::process::Command;
142    /// use pwner::Spawner;
143    ///
144    /// let mut command = Command::new("ls");
145    /// if let Ok(child) = command.spawn_owned() {
146    ///     match child.id() {
147    ///       Some(pid) => println!("Child's ID is {}", pid),
148    ///       None => println!("Child has already exited"),
149    ///     }
150    /// } else {
151    ///     println!("ls command didn't start");
152    /// }
153    /// ```
154    #[must_use]
155    pub fn id(&self) -> Option<u32> {
156        self.0.id()
157    }
158
159    /// Choose which pipe to read form next.
160    ///
161    /// # Examples
162    ///
163    /// Basic usage:
164    ///
165    /// ```no_run
166    /// # async {
167    /// use tokio::io::AsyncReadExt;
168    /// use tokio::process::Command;
169    /// use pwner::Spawner;
170    /// use pwner::tokio::ReadSource;
171    ///
172    /// let mut child = Command::new("ls").spawn_owned().unwrap();
173    /// let mut buffer = [0_u8; 1024];
174    ///
175    /// child.read_from(ReadSource::Both).read(&mut buffer).await.unwrap();
176    /// # };
177    /// ```
178    pub fn read_from(&mut self, read_source: ReadSource) -> &mut Self {
179        self.1.read_from(read_source);
180        self
181    }
182
183    /// Waits for the child to exit completely, returning the status with which it exited, stdout,
184    /// and stderr.
185    ///
186    /// The stdin handle to the child process, if any, will be closed before waiting. This helps
187    /// avoid deadlock: it ensures that the child does not block waiting for input from the parent,
188    /// while the parent waits for the child to exit.
189    ///
190    /// # Examples
191    ///
192    /// Basic usage:
193    ///
194    /// ```no_run
195    /// # async {
196    /// use pwner::Spawner;
197    /// use tokio::io::{AsyncReadExt, BufReader};
198    /// use tokio::process::Command;
199    ///
200    /// let child = Command::new("ls").spawn_owned().unwrap();
201    /// let (status, stdout, stderr) = child.wait().await.unwrap();
202    ///
203    /// let mut buffer = String::new();
204    /// if status.success() {
205    ///     let mut reader = BufReader::new(stdout);
206    ///     reader.read_to_string(&mut buffer).await.unwrap();
207    /// } else {
208    ///     let mut reader = BufReader::new(stderr);
209    ///     reader.read_to_string(&mut buffer).await.unwrap();
210    /// }
211    /// # };
212    /// ```
213    ///
214    /// # Errors
215    ///
216    /// Relays the error from [`tokio::process::Child::wait()`]
217    pub async fn wait(
218        self,
219    ) -> Result<
220        (
221            std::process::ExitStatus,
222            tokio::process::ChildStdout,
223            tokio::process::ChildStderr,
224        ),
225        std::io::Error,
226    > {
227        let (mut child, _, stdout, stderr) = self.eject();
228        child.wait().await.map(|status| (status, stdout, stderr))
229    }
230
231    /// Decomposes the handle into mutable references to the pipes.
232    ///
233    /// # Examples
234    ///
235    /// Basic usage:
236    ///
237    /// ```no_run
238    /// # async {
239    /// use tokio::io::{ AsyncReadExt, AsyncWriteExt };
240    /// use tokio::process::Command;
241    /// use pwner::Spawner;
242    ///
243    /// let mut child = Command::new("cat").spawn_owned().unwrap();
244    /// let mut buffer = [0_u8; 1024];
245    /// let (stdin, stdout, _) = child.pipes();
246    ///
247    /// stdin.write_all(b"hello\n").await.unwrap();
248    /// stdout.read(&mut buffer).await.unwrap();
249    /// # };
250    /// ```
251    pub fn pipes(
252        &mut self,
253    ) -> (
254        &mut tokio::process::ChildStdin,
255        &mut tokio::process::ChildStdout,
256        &mut tokio::process::ChildStderr,
257    ) {
258        (self.0.stdin(), &mut self.1.stdout, &mut self.1.stderr)
259    }
260
261    /// Separates the process and its input from the output pipes. Ownership is retained by a
262    /// [`Simplex`] which still implements a graceful drop of the child process.
263    ///
264    /// # Examples
265    ///
266    /// Basic usage:
267    ///
268    /// ```no_run
269    /// # async {
270    /// use tokio::io::{ AsyncReadExt, AsyncWriteExt };
271    /// use tokio::process::Command;
272    /// use pwner::Spawner;
273    ///
274    /// let child = Command::new("cat").spawn_owned().unwrap();
275    /// let (mut input_only_process, mut output) = child.decompose();
276    ///
277    /// // Spawn printing task
278    /// tokio::spawn(async move {
279    ///     let mut buffer = [0; 1024];
280    ///     while let Ok(bytes) = output.read(&mut buffer).await {
281    ///         if let Ok(string) = std::str::from_utf8(&buffer[..bytes]) {
282    ///             print!("{}", string);
283    ///         }
284    ///     }
285    /// });
286    ///
287    /// // Interact normally with the child process
288    /// input_only_process.write_all(b"hello\n").await.unwrap();
289    /// # };
290    /// ```
291    #[must_use]
292    pub fn decompose(self) -> (Simplex, Output) {
293        (self.0, self.1)
294    }
295
296    /// Completely releases the ownership of the child process. The raw underlying process and
297    /// pipes are returned and no wrapping function is applicable any longer.
298    ///
299    /// **Note:** By ejecting the process, graceful drop will no longer be available.
300    ///
301    /// # Examples
302    ///
303    /// Basic usage:
304    ///
305    /// ```no_run
306    /// # async {
307    /// use tokio::io::{ AsyncReadExt, AsyncWriteExt };
308    /// use tokio::process::Command;
309    /// use pwner::Spawner;
310    ///
311    /// let mut child = Command::new("cat").spawn_owned().unwrap();
312    /// let mut buffer = [0_u8; 1024];
313    /// let (process, mut stdin, mut stdout, _) = child.eject();
314    ///
315    /// stdin.write_all(b"hello\n").await.unwrap();
316    /// stdout.read(&mut buffer).await.unwrap();
317    ///
318    /// // Graceful drop will not be executed for `child` as the ejected variable leaves scope here
319    /// # };
320    /// ```
321    #[must_use]
322    pub fn eject(
323        self,
324    ) -> (
325        tokio::process::Child,
326        tokio::process::ChildStdin,
327        tokio::process::ChildStdout,
328        tokio::process::ChildStderr,
329    ) {
330        let (process, stdin) = self.0.eject();
331        let (stdout, stderr) = self.1.eject();
332        (process, stdin, stdout, stderr)
333    }
334
335    /// Consumes the process to allow awaiting for shutdown.
336    ///
337    /// This method is essentially the same as a `drop`, however it return a `Future` which allows
338    /// the parent to await the shutdown.
339    ///
340    /// # Examples
341    ///
342    /// Basic usage:
343    ///
344    /// ```no_run
345    /// # async {
346    /// use tokio::process::Command;
347    /// use pwner::Spawner;
348    ///
349    /// let child = Command::new("top").spawn_owned().unwrap();
350    /// child.shutdown().await.unwrap();
351    /// # };
352    /// ```
353    ///
354    /// # Errors
355    ///
356    /// * [`std::io::Error`] if failure when killing the process.
357    pub async fn shutdown(self) -> std::io::Result<std::process::ExitStatus> {
358        self.0.shutdown().await
359    }
360}
361
362impl tokio::io::AsyncWrite for Duplex {
363    fn poll_write(
364        mut self: std::pin::Pin<&mut Self>,
365        ctx: &mut std::task::Context<'_>,
366        buf: &[u8],
367    ) -> std::task::Poll<std::io::Result<usize>> {
368        std::pin::Pin::new(&mut self.0).poll_write(ctx, buf)
369    }
370
371    fn poll_flush(
372        mut self: std::pin::Pin<&mut Self>,
373        ctx: &mut std::task::Context<'_>,
374    ) -> std::task::Poll<std::io::Result<()>> {
375        std::pin::Pin::new(&mut self.0).poll_flush(ctx)
376    }
377
378    fn poll_shutdown(
379        mut self: std::pin::Pin<&mut Self>,
380        ctx: &mut std::task::Context<'_>,
381    ) -> std::task::Poll<std::io::Result<()>> {
382        std::pin::Pin::new(&mut self.0).poll_shutdown(ctx)
383    }
384}
385
386impl tokio::io::AsyncRead for Duplex {
387    fn poll_read(
388        mut self: std::pin::Pin<&mut Self>,
389        ctx: &mut std::task::Context<'_>,
390        buf: &mut tokio::io::ReadBuf<'_>,
391    ) -> std::task::Poll<std::io::Result<()>> {
392        std::pin::Pin::new(&mut self.1).poll_read(ctx, buf)
393    }
394}
395
396/// An implementation of [`Process`](crate::Process) that is stripped from any output
397/// pipes.
398///
399/// All write operations are async.
400///
401/// # Examples
402///
403/// ```no_run
404/// use tokio::process::Command;
405/// use pwner::Spawner;
406///
407/// let mut command = Command::new("ls");
408/// if let Ok(child) = command.spawn_owned() {
409///     let (input_only_process, _) = child.decompose();
410/// } else {
411///     println!("ls command didn't start");
412/// }
413/// ```
414///
415/// **Note:** On *nix platforms, the owned process will have 2 seconds between signals, which is a
416/// blocking wait.
417#[allow(clippy::module_name_repetitions)]
418pub struct Simplex(Option<ProcessImpl>);
419
420impl super::Process for Simplex {}
421
422impl Simplex {
423    /// Returns the OS-assigned process identifier associated with this child.
424    ///
425    /// # Examples
426    ///
427    /// Basic usage:
428    ///
429    /// ```no_run
430    /// use tokio::process::Command;
431    /// use pwner::Spawner;
432    ///
433    /// let mut command = Command::new("ls");
434    /// if let Ok(child) = command.spawn_owned() {
435    ///     let (process, _) = child.decompose();
436    ///     match process.id() {
437    ///       Some(pid) => println!("Child's ID is {}", pid),
438    ///       None => println!("Child has already exited"),
439    ///     }
440    /// } else {
441    ///     println!("ls command didn't start");
442    /// }
443    /// ```
444    #[must_use]
445    pub fn id(&self) -> Option<u32> {
446        self.0
447            .as_ref()
448            .unwrap_or_else(|| unreachable!())
449            .process
450            .id()
451    }
452
453    fn stdin(&mut self) -> &mut tokio::process::ChildStdin {
454        &mut self.0.as_mut().unwrap().stdin
455    }
456
457    /// Waits for the child to exit completely, returning the status with which it exited.
458    ///
459    /// The stdin handle to the child process, if any, will be closed before waiting. This helps
460    /// avoid deadlock: it ensures that the child does not block waiting for input from the parent,
461    /// while the parent waits for the child to exit.
462    ///
463    /// # Examples
464    ///
465    /// Basic usage:
466    ///
467    /// ```no_run
468    /// # async {
469    /// use pwner::{tokio::ReadSource, Spawner};
470    /// use tokio::io::{AsyncReadExt, AsyncWriteExt, BufReader};
471    /// use tokio::process::Command;
472    ///
473    /// let (mut child, mut output) = Command::new("cat").spawn_owned().unwrap().decompose();
474    ///
475    /// child.write_all(b"Hello\n").await.unwrap();
476    /// let status = child.wait().await.unwrap();
477    ///
478    /// let mut buffer = String::new();
479    /// if status.success() {
480    ///     output.read_from(ReadSource::Stdout);
481    /// } else {
482    ///     output.read_from(ReadSource::Stderr);
483    /// }
484    /// let mut reader = BufReader::new(output);
485    /// reader.read_to_string(&mut buffer).await.unwrap();
486    /// # };
487    /// ```
488    ///
489    /// # Errors
490    ///
491    /// Relays the error from [`tokio::process::Child::wait()`]
492    pub async fn wait(self) -> Result<std::process::ExitStatus, std::io::Error> {
493        let (mut child, _) = self.eject();
494        child.wait().await
495    }
496
497    /// Completely releases the ownership of the child process. The raw underlying process and
498    /// pipes are returned and no wrapping function is applicable any longer.
499    ///
500    /// **Note:** By ejecting the process, graceful drop will no longer be available.
501    ///
502    /// # Examples
503    ///
504    /// Basic usage:
505    ///
506    /// ```no_run
507    /// # async {
508    /// use tokio::io::{ AsyncReadExt, AsyncWriteExt };
509    /// use tokio::process::Command;
510    /// use pwner::Spawner;
511    ///
512    /// let (child, mut output) = Command::new("cat").spawn_owned().unwrap().decompose();
513    /// let mut buffer = [0_u8; 1024];
514    /// let (process, mut stdin) = child.eject();
515    ///
516    /// stdin.write_all(b"hello\n").await.unwrap();
517    /// output.read(&mut buffer).await.unwrap();
518    ///
519    /// // Graceful drop will not be executed for `child` as the ejected variable leaves scope here
520    /// # };
521    /// ```
522    #[must_use]
523    pub fn eject(mut self) -> (tokio::process::Child, tokio::process::ChildStdin) {
524        let process = self.0.take().unwrap_or_else(|| unreachable!());
525        (process.process, process.stdin)
526    }
527
528    /// Consumes the process to allow awaiting for shutdown.
529    ///
530    /// This method is essentially the same as a `drop`, however it return a `Future` which allows
531    /// the parent to await the shutdown.
532    ///
533    /// # Examples
534    ///
535    /// Basic usage:
536    ///
537    /// ```no_run
538    /// # async {
539    /// use tokio::process::Command;
540    /// use pwner::Spawner;
541    ///
542    /// let (child, _) = Command::new("top").spawn_owned().unwrap().decompose();
543    /// child.shutdown().await.unwrap();
544    /// # };
545    /// ```
546    ///
547    /// # Errors
548    ///
549    /// If failure when killing the process.
550    pub async fn shutdown(mut self) -> std::io::Result<std::process::ExitStatus> {
551        match self
552            .0
553            .take()
554            .unwrap_or_else(|| unreachable!())
555            .shutdown()
556            .await
557        {
558            Ok(status) => Ok(status),
559            Err(super::UnixIoError::Io(err)) => Err(err),
560            Err(super::UnixIoError::Unix(err)) => {
561                Err(std::io::Error::from_raw_os_error(err as i32))
562            }
563        }
564    }
565}
566
567impl std::ops::Drop for Simplex {
568    fn drop(&mut self) {
569        if self.0.is_some() {
570            tokio::spawn(self.0.take().unwrap().shutdown());
571        }
572    }
573}
574
575impl tokio::io::AsyncWrite for Simplex {
576    fn poll_write(
577        mut self: std::pin::Pin<&mut Self>,
578        ctx: &mut std::task::Context<'_>,
579        buf: &[u8],
580    ) -> std::task::Poll<std::io::Result<usize>> {
581        std::pin::Pin::new(&mut self.0.as_mut().unwrap().stdin).poll_write(ctx, buf)
582    }
583
584    fn poll_flush(
585        mut self: std::pin::Pin<&mut Self>,
586        ctx: &mut std::task::Context<'_>,
587    ) -> std::task::Poll<std::io::Result<()>> {
588        std::pin::Pin::new(&mut self.0.as_mut().unwrap().stdin).poll_flush(ctx)
589    }
590
591    fn poll_shutdown(
592        mut self: std::pin::Pin<&mut Self>,
593        ctx: &mut std::task::Context<'_>,
594    ) -> std::task::Poll<std::io::Result<()>> {
595        std::pin::Pin::new(&mut self.0.as_mut().unwrap().stdin).poll_shutdown(ctx)
596    }
597}
598
599/// A readable handle for both the stdout and stderr of the child process.
600pub struct Output {
601    read_source: ReadSource,
602    stdout: tokio::process::ChildStdout,
603    stderr: tokio::process::ChildStderr,
604}
605
606impl Output {
607    /// Choose which pipe to read form next.
608    ///
609    /// # Examples
610    ///
611    /// Basic usage:
612    ///
613    /// ```no_run
614    /// # async {
615    /// use tokio::io::AsyncReadExt;
616    /// use tokio::process::Command;
617    /// use pwner::Spawner;
618    /// use pwner::tokio::ReadSource;
619    ///
620    /// let (process, mut output) = Command::new("ls").spawn_owned().unwrap().decompose();
621    /// let mut buffer = [0_u8; 1024];
622    /// output.read_from(ReadSource::Stdout).read(&mut buffer).await.unwrap();
623    /// # };
624    /// ```
625    pub fn read_from(&mut self, read_source: ReadSource) -> &mut Self {
626        self.read_source = read_source;
627        self
628    }
629
630    /// Decomposes the handle into mutable references to the pipes.
631    ///
632    /// # Examples
633    ///
634    /// Basic usage:
635    ///
636    /// ```no_run
637    /// # async {
638    /// use tokio::io::AsyncReadExt;
639    /// use tokio::process::Command;
640    /// use pwner::Spawner;
641    ///
642    /// let (process, mut output) = Command::new("ls").spawn_owned().unwrap().decompose();
643    /// let mut buffer = [0_u8; 1024];
644    /// let (stdout, stderr) = output.pipes();
645    ///
646    /// stdout.read(&mut buffer).await.unwrap();
647    /// # };
648    /// ```
649    pub fn pipes(
650        &mut self,
651    ) -> (
652        &mut tokio::process::ChildStdout,
653        &mut tokio::process::ChildStderr,
654    ) {
655        (&mut self.stdout, &mut self.stderr)
656    }
657
658    /// Consumes this struct returning the containing pipes.
659    #[must_use]
660    pub fn eject(self) -> (tokio::process::ChildStdout, tokio::process::ChildStderr) {
661        (self.stdout, self.stderr)
662    }
663}
664
665impl tokio::io::AsyncRead for Output {
666    fn poll_read(
667        mut self: std::pin::Pin<&mut Self>,
668        cx: &mut std::task::Context<'_>,
669        buf: &mut tokio::io::ReadBuf<'_>,
670    ) -> std::task::Poll<std::io::Result<()>> {
671        match self.read_source {
672            ReadSource::Stdout => std::pin::Pin::new(&mut self.stdout).poll_read(cx, buf),
673            ReadSource::Stderr => std::pin::Pin::new(&mut self.stderr).poll_read(cx, buf),
674            ReadSource::Both => {
675                let stderr = std::pin::Pin::new(&mut self.stderr).poll_read(cx, buf);
676                if stderr.is_ready() {
677                    stderr
678                } else {
679                    std::pin::Pin::new(&mut self.stdout).poll_read(cx, buf)
680                }
681            }
682        }
683    }
684}
685
686struct ProcessImpl {
687    process: tokio::process::Child,
688    stdin: tokio::process::ChildStdin,
689}
690
691impl ProcessImpl {
692    // Allowed because we are already assuming *nix
693    #[allow(clippy::cast_possible_wrap)]
694    #[cfg(unix)]
695    pub fn pid(&self) -> Option<nix::unistd::Pid> {
696        self.process
697            .id()
698            .map(|pid| nix::unistd::Pid::from_raw(pid as nix::libc::pid_t))
699    }
700
701    #[cfg(not(unix))]
702    async fn shutdown(mut self) -> std::io::Result<std::process::ExitStatus> {
703        self.process.kill();
704        self.process.await
705    }
706
707    #[cfg(unix)]
708    async fn shutdown(mut self) -> Result<std::process::ExitStatus, super::UnixIoError> {
709        // Copy the pid if the child has not exited yet
710        let pid = match self.process.try_wait() {
711            Ok(None) => self.pid().unwrap(),
712            Ok(Some(status)) => return Ok(status),
713            Err(err) => return Err(super::UnixIoError::from(err)),
714        };
715
716        // Pin the process
717        let mut process = self.process;
718        let mut process = std::pin::Pin::new(&mut process);
719
720        {
721            use nix::sys::signal;
722            use std::time::Duration;
723            use tokio::time::timeout;
724
725            if timeout(Duration::from_secs(2), process.wait())
726                .await
727                .is_err()
728            {
729                // Try SIGINT
730                signal::kill(pid, signal::SIGINT)?;
731            }
732
733            if timeout(Duration::from_secs(2), process.wait())
734                .await
735                .is_err()
736            {
737                // Try SIGTERM
738                signal::kill(pid, signal::SIGTERM)?;
739            }
740
741            if timeout(Duration::from_secs(2), process.wait())
742                .await
743                .is_err()
744            {
745                // Go for the kill
746                process.kill().await?;
747            }
748        }
749
750        // Block until process is freed
751        process.wait().await.map_err(super::UnixIoError::from)
752    }
753}
754
755#[cfg(all(test, unix))]
756mod test {
757    use crate::Spawner;
758
759    #[tokio::test]
760    async fn read() {
761        use tokio::io::AsyncReadExt;
762
763        let mut child = tokio::process::Command::new("sh")
764            .arg("-c")
765            .arg("echo hello")
766            .spawn_owned()
767            .unwrap();
768        let mut output = String::new();
769        assert!(child.read_to_string(&mut output).await.is_ok());
770
771        assert_eq!("hello\n", output);
772    }
773
774    #[tokio::test]
775    async fn write() {
776        use tokio::io::{AsyncReadExt, AsyncWriteExt};
777
778        let mut child = tokio::process::Command::new("cat").spawn_owned().unwrap();
779        assert!(child.write_all(b"hello\n").await.is_ok());
780
781        let mut buffer = [0_u8; 10];
782        let bytes = child.read(&mut buffer).await.unwrap();
783        assert_eq!("hello\n", std::str::from_utf8(&buffer[..bytes]).unwrap());
784    }
785
786    #[tokio::test]
787    async fn test_drop_does_not_panic() {
788        let child = tokio::process::Command::new("ls").spawn_owned().unwrap();
789        let mut child = child.0;
790        assert!(child.0.take().unwrap().shutdown().await.is_ok());
791    }
792}