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}