diff --git a/tokio-process/Cargo.toml b/tokio-process/Cargo.toml index 0b2fcd636..eb998c1d7 100644 --- a/tokio-process/Cargo.toml +++ b/tokio-process/Cargo.toml @@ -4,7 +4,8 @@ name = "tokio-process" # - Update html_root_url. # - Update CHANGELOG.md. # - Create "X.Y.Z" git tag. -version = "0.2.4" +version = "0.3.0" +publish = false edition = "2018" authors = ["Tokio Contributors "] license = "MIT" @@ -17,19 +18,21 @@ An implementation of an asynchronous process management backed futures. categories = ["asynchronous"] [dependencies] -futures-util-preview = { version = "0.3.0-alpha.17", features = ["io"] } futures-core-preview = { version = "0.3.0-alpha.17" } -tokio-io = { version = "0.2.0", path = "../tokio-io" } -tokio-reactor = { version = "0.2.0", path = "../tokio-reactor" } -tokio-sync = { version = "0.2.0", path = "../tokio-sync" } - -[dev-dependencies] -failure = "0.1" +futures-util-preview = { version = "0.3.0-alpha.17" } log = "0.4" -tokio = { version = "0.2.0", path = "../tokio" } +tokio-io = { version = "0.2.0", path = "../tokio-io", features = ["util"] } +tokio-reactor = { version = "0.2.0", path = "../tokio-reactor" } + +[dev-dependencies.tokio] +version = "0.2.0" +path = "../tokio" +default-features = false +features = ["codec", "rt-full"] [target.'cfg(windows)'.dependencies] mio-named-pipes = "0.1" +tokio-sync = { version = "0.2.0", path = "../tokio-sync" } [target.'cfg(windows)'.dependencies.winapi] version = "0.3" diff --git a/tokio-process/src/lib.rs b/tokio-process/src/lib.rs index 6bc94ef07..55e0eb28a 100644 --- a/tokio-process/src/lib.rs +++ b/tokio-process/src/lib.rs @@ -10,30 +10,28 @@ //! # Examples //! //! Here's an example program which will spawn `echo hello world` and then wait -//! for it using an event loop. +//! for it complete. //! //! ```no_run -//! extern crate tokio; -//! extern crate tokio_process; +//! #![feature(async_await)] //! //! use std::process::Command; -//! -//! use futures_util::future::FutureExt; //! use tokio_process::CommandExt; //! -//! fn main() { +//! #[tokio::main] +//! async fn main() -> Result<(), Box> { //! // Use the standard library's `Command` type to build a process and //! // then execute it via the `CommandExt` trait. //! let child = Command::new("echo").arg("hello").arg("world") //! .spawn_async(); //! //! // Make sure our child succeeded in spawning and process the result -//! let future = child.expect("failed to spawn") -//! .map(|status| println!("exit status: {}", status)) -//! .map_err(|e| panic!("failed to wait for exit: {}", e)); +//! let future = child.expect("failed to spawn"); //! -//! // Send the future to the tokio runtime for execution -//! tokio::run(future) +//! // Await until the future (and the command) completes +//! let status = future.await?; +//! println!("the command exited with: {}", status); +//! Ok(()) //! } //! ``` //! @@ -41,104 +39,69 @@ //! world` but we also capture its output. //! //! ```no_run -//! extern crate tokio; -//! extern crate tokio_process; +//! #![feature(async_await)] //! //! use std::process::Command; -//! -//! use futures_util::future::FutureExt; //! use tokio_process::CommandExt; //! -//! fn main() { +//! #[tokio::main] +//! async fn main() -> Result<(), Box> { //! // Like above, but use `output_async` which returns a future instead of //! // immediately returning the `Child`. //! let output = Command::new("echo").arg("hello").arg("world") //! .output_async(); //! -//! let future = output.map_err(|e| panic!("failed to collect output: {}", e)) -//! .map(|output| { -//! assert!(output.status.success()); -//! assert_eq!(output.stdout, b"hello world\n"); -//! }); +//! let output = output.await?; //! -//! tokio::run(future); +//! assert!(output.status.success()); +//! assert_eq!(output.stdout, b"hello world\n"); +//! Ok(()) //! } //! ``` //! //! We can also read input line by line. //! //! ```no_run -//! extern crate failure; -//! extern crate tokio; -//! extern crate tokio_process; -//! extern crate tokio_io; +//! #![feature(async_await)] //! -//! use failure::Error; -//! use futures_util::future::FutureExt; //! use futures_util::stream::StreamExt; -//! use std::io::BufReader; //! use std::process::{Command, Stdio}; -//! use tokio_process::{Child, ChildStdout, CommandExt}; +//! use tokio::codec::{FramedRead, LinesCodec}; +//! use tokio_process::CommandExt; +//! +//! #[tokio::main] +//! async fn main() -> Result<(), Box> { +//! let mut cmd = Command::new("cat"); +//! +//! // Specify that we want the command's standard output piped back to us. +//! // By default, standard input/output/error will be inherited from the +//! // current process (for example, this means that standard input will +//! // come from the keyboard and standard output/error will go directly to +//! // the terminal if this process is invoked from the command line). +//! cmd.stdout(Stdio::piped()); +//! +//! let mut child = cmd.spawn_async() +//! .expect("failed to spawn command"); //! -//! fn lines_stream(child: &mut Child) -> impl Stream + Send + 'static { //! let stdout = child.stdout().take() //! .expect("child did not have a handle to stdout"); //! -//! tokio_io::io::lines(BufReader::new(stdout)) -//! // Convert any io::Error into a failure::Error for better flexibility -//! .map_err(|e| Error::from(e)) -//! // We print each line we've received here as an example of a way we can -//! // do something with the data. This can be changed to map the data to -//! // something else, or to consume it differently. -//! .inspect(|line| println!("Line: {}", line)) -//! } +//! let mut reader = FramedRead::new(stdout, LinesCodec::new()); //! -//! fn main() { -//! // Lazily invoke any code so it can run directly within the tokio runtime -//! tokio::run(futures::lazy(|| { -//! let mut cmd = Command::new("cat"); +//! // Ensure the child process is spawned in the runtime so it can +//! // make progress on its own while we await for any output. +//! tokio::spawn(async { +//! let status = child.await +//! .expect("child process encountered an error"); //! -//! // Specify that we want the command's standard output piped back to us. -//! // By default, standard input/output/error will be inherited from the -//! // current process (for example, this means that standard input will -//! // come from the keyboard and standard output/error will go directly to -//! // the terminal if this process is invoked from the command line). -//! cmd.stdout(Stdio::piped()); +//! println!("child status was: {}", status); +//! }); //! -//! let mut child = cmd.spawn_async() -//! .expect("failed to spawn command"); +//! while let Some(line) = reader.next().await { +//! println!("Line: {}", line?); +//! } //! -//! let lines = lines_stream(&mut child); -//! -//! // Spawning into the tokio runtime requires that the future's Item and -//! // Error are both `()`. This is because tokio doesn't know what to do -//! // with any results or errors, so it requires that we've handled them! -//! // -//! // We can replace these sample usages of the child's exit status (or -//! // an encountered error) perform some different actions if needed! -//! // For example, log the error, or send a message on a channel, etc. -//! let child_future = child -//! .map(|status| println!("child status was: {}", status)) -//! .map_err(|e| panic!("error while running child: {}", e)); -//! -//! // Ensure the child process can live on within the runtime, otherwise -//! // the process will get killed if this handle is dropped -//! tokio::spawn(child_future); -//! -//! // Return a future to tokio. This is the same as calling using -//! // `tokio::spawn` above, but without having to return a dummy future -//! // here. -//! lines -//! // Convert the stream of values into a future which will resolve -//! // once the entire stream has been consumed. In this example we -//! // don't need to do anything with the data within the `for_each` -//! // call, but you can extend this to do something else (keep in mind -//! // that the stream will not produce items until the future returned -//! // from the closure resolves). -//! .for_each(|_| Ok(())) -//! // Similarly we "handle" any errors that arise, as required by tokio. -//! .map_err(|e| panic!("error while processing lines: {}", e)) -//! })); +//! Ok(()) //! } //! ``` //! @@ -152,14 +115,12 @@ //! `tokio_process::Child` is dropped. The behavior of the standard library can //! be regained with the `Child::forget` method. -#![warn(missing_debug_implementations)] -#![deny(missing_docs)] -#![doc(html_root_url = "https://docs.rs/tokio-process/0.2")] +#![doc(html_root_url = "https://docs.rs/tokio-process/0.3.0")] +#![deny(missing_debug_implementations, missing_docs, rust_2018_idioms)] +#![cfg_attr(test, deny(warnings))] +#![doc(test(no_crate_inject, attr(deny(rust_2018_idioms))))] #![feature(async_await)] -extern crate tokio_io; -extern crate tokio_reactor; - #[cfg(unix)] #[macro_use] extern crate lazy_static; @@ -173,7 +134,6 @@ use std::process::{Command, ExitStatus, Output, Stdio}; use futures_core::future::TryFuture; use futures_util::future; use futures_util::future::FutureExt; -use futures_util::io::{AsyncRead, AsyncWrite}; use futures_util::try_future::TryFutureExt; use kill::Kill; @@ -182,8 +142,7 @@ use std::future::Future; use std::pin::Pin; use std::task::Context; use std::task::Poll; -use tokio_io::AsyncRead as TokioAsyncRead; -use tokio_io::AsyncWrite as TokioAsyncWrite; +use tokio_io::{AsyncRead, AsyncReadExt, AsyncWrite}; use tokio_reactor::Handle; #[path = "unix/mod.rs"] @@ -421,13 +380,12 @@ impl Drop for ChildDropGuard { impl Future for ChildDropGuard { type Output = Result; - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { - let inner = Pin::get_mut(self); - let ret = inner.inner.try_poll_unpin(cx); + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let ret = Pin::new(&mut self.inner).try_poll(cx); if let Poll::Ready(Ok(_)) = ret { // Avoid the overhead of trying to kill a reaped process - inner.kill_on_drop = false; + self.kill_on_drop = false; } ret @@ -511,7 +469,7 @@ impl Child { match stdout_val { Some(mut io) => { let mut vec = Vec::new(); - futures_util::io::AsyncReadExt::read_to_end(&mut io, &mut vec).await?; + AsyncReadExt::read_to_end(&mut io, &mut vec).await?; Ok(vec) } None => Ok(Vec::new()), @@ -521,7 +479,7 @@ impl Child { match stderr_val { Some(mut io) => { let mut vec = Vec::new(); - futures_util::io::AsyncReadExt::read_to_end(&mut io, &mut vec).await?; + AsyncReadExt::read_to_end(&mut io, &mut vec).await?; Ok(vec) } None => Ok(Vec::new()), @@ -552,25 +510,20 @@ impl Child { /// > `Child` instance into an event loop as an alternative to this method. /// /// ```no_run - /// # extern crate tokio; - /// # extern crate tokio_process; - /// # + /// # #![feature(async_await)] /// # use std::process::Command; - /// # - /// # use futures_util::future::FutureExt; /// # use tokio_process::CommandExt; - /// # - /// # fn main() { + /// + /// # #[tokio::main] + /// # async fn main() { /// let child = Command::new("echo").arg("hello").arg("world") /// .spawn_async() /// .expect("failed to spawn"); /// - /// let do_cleanup = child.map(|_| ()) // Ignore result - /// .map_err(|_| ()); // Ignore errors - /// - /// tokio::spawn(do_cleanup); + /// tokio::spawn(async { + /// let _ = child.await; + /// }); /// # } - /// ``` pub fn forget(mut self) { self.child.forget(); } @@ -579,8 +532,8 @@ impl Child { impl Future for Child { type Output = io::Result; - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { - Pin::get_mut(self).child.poll_unpin(cx) + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + Pin::new(&mut self.child).poll(cx) } } @@ -594,7 +547,7 @@ pub struct WaitWithOutput { } impl fmt::Debug for WaitWithOutput { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt.debug_struct("WaitWithOutput") .field("inner", &"..") .finish() @@ -604,15 +557,11 @@ impl fmt::Debug for WaitWithOutput { impl Future for WaitWithOutput { type Output = io::Result; - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { - Pin::get_mut(self).inner.poll_unpin(cx) + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + Pin::new(&mut self.inner).poll(cx) } } -#[doc(hidden)] -#[deprecated(note = "renamed to `StatusAsync`", since = "0.2.1")] -pub type StatusAsync2 = StatusAsync; - /// Future returned by the `CommandExt::status_async` method. /// /// This future is used to conveniently spawn a child and simply wait for its @@ -627,8 +576,8 @@ pub struct StatusAsync { impl Future for StatusAsync { type Output = io::Result; - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { - Pin::get_mut(self).inner.poll_unpin(cx) + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + Pin::new(&mut self.inner).poll(cx) } } @@ -643,7 +592,7 @@ pub struct OutputAsync { } impl fmt::Debug for OutputAsync { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt.debug_struct("OutputAsync") .field("inner", &"..") .finish() @@ -653,8 +602,8 @@ impl fmt::Debug for OutputAsync { impl Future for OutputAsync { type Output = io::Result; - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { - Pin::get_mut(self).inner.poll_unpin(cx) + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + Pin::new(&mut self.inner).poll(cx) } } @@ -701,16 +650,20 @@ impl Write for ChildStdin { } impl AsyncWrite for ChildStdin { - fn poll_write(self: Pin<&mut Self>, cx: &mut Context, buf: &[u8]) -> Poll> { - Pin::new(&mut Pin::get_mut(self).inner).poll_write(cx, buf) + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + Pin::new(&mut self.inner).poll_write(cx, buf) } - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - Pin::new(&mut Pin::get_mut(self).inner).poll_flush(cx) + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.inner).poll_flush(cx) } - fn poll_close(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - Pin::new(&mut Pin::get_mut(self).inner).poll_shutdown(cx) + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.inner).poll_shutdown(cx) } } @@ -722,11 +675,11 @@ impl Read for ChildStdout { impl AsyncRead for ChildStdout { fn poll_read( - self: Pin<&mut Self>, - cx: &mut Context, + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { - Pin::new(&mut Pin::get_mut(self).inner).poll_read(cx, buf) + Pin::new(&mut self.inner).poll_read(cx, buf) } } @@ -738,11 +691,11 @@ impl Read for ChildStderr { impl AsyncRead for ChildStderr { fn poll_read( - self: Pin<&mut Self>, - cx: &mut Context, + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { - Pin::new(&mut Pin::get_mut(self).inner).poll_read(cx, buf) + Pin::new(&mut self.inner).poll_read(cx, buf) } } @@ -835,7 +788,7 @@ mod test { impl Future for Mock { type Output = Result<(), ()>; - fn poll(self: Pin<&mut Self>, _cx: &mut Context) -> Poll { + fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { let inner = Pin::get_mut(self); inner.num_polls += 1; inner.poll_result diff --git a/tokio-process/src/unix/mod.rs b/tokio-process/src/unix/mod.rs index 140c85daf..434f2d234 100644 --- a/tokio-process/src/unix/mod.rs +++ b/tokio-process/src/unix/mod.rs @@ -21,21 +21,16 @@ //! processes in general aren't scalable (e.g. millions) so it shouldn't be that //! bad in theory... -extern crate libc; -extern crate mio; -extern crate tokio_signal; - mod orphan; mod reap; -use self::mio::event::Evented; -use self::mio::unix::{EventedFd, UnixReady}; -use self::mio::{Poll as MioPoll, PollOpt, Ready, Token}; use self::orphan::{AtomicOrphanQueue, OrphanQueue, Wait}; use self::reap::Reaper; use super::SpawnedChild; use crate::kill::Kill; -use futures_util::future::FutureExt; +use mio::event::Evented; +use mio::unix::{EventedFd, UnixReady}; +use mio::{Poll as MioPoll, PollOpt, Ready, Token}; use std::fmt; use std::future::Future; use std::io; @@ -70,7 +65,7 @@ lazy_static! { struct GlobalOrphanQueue; impl fmt::Debug for GlobalOrphanQueue { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { ORPHAN_QUEUE.fmt(fmt) } } @@ -91,7 +86,7 @@ pub struct Child { } impl fmt::Debug for Child { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt.debug_struct("Child") .field("pid", &self.inner.id()) .finish() @@ -131,8 +126,8 @@ impl Kill for Child { impl Future for Child { type Output = io::Result; - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { - (&mut Pin::get_mut(self).inner).poll_unpin(cx) + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + Pin::new(&mut self.inner).poll(cx) } } diff --git a/tokio-process/src/unix/orphan.rs b/tokio-process/src/unix/orphan.rs index 6f456ce0e..011cdfc7e 100644 --- a/tokio-process/src/unix/orphan.rs +++ b/tokio-process/src/unix/orphan.rs @@ -1,6 +1,4 @@ -extern crate crossbeam_queue; - -use self::crossbeam_queue::SegQueue; +use crossbeam_queue::SegQueue; use std::io; use std::process::ExitStatus; diff --git a/tokio-process/src/unix/reap.rs b/tokio-process/src/unix/reap.rs index 693507e43..46ee40da0 100644 --- a/tokio-process/src/unix/reap.rs +++ b/tokio-process/src/unix/reap.rs @@ -64,7 +64,7 @@ where { type Output = io::Result; - fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { loop { // If the child hasn't exited yet, then it's our responsibility to // ensure the current task gets notified when it might be able to @@ -203,7 +203,7 @@ mod test { impl Stream for MockStream { type Item = io::Result<()>; - fn poll_next(self: Pin<&mut Self>, _cx: &mut Context) -> Poll> { + fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { let inner = Pin::get_mut(self); inner.total_polls += 1; match inner.values.remove(0) { diff --git a/tokio-process/src/windows.rs b/tokio-process/src/windows.rs index 56aab82b7..236ea471e 100644 --- a/tokio-process/src/windows.rs +++ b/tokio-process/src/windows.rs @@ -15,9 +15,6 @@ //! `RegisterWaitForSingleObject` and then wait on the other end of the oneshot //! from then on out. -extern crate mio_named_pipes; -extern crate winapi; - use crate::kill::Kill; use std::fmt; @@ -34,18 +31,18 @@ use std::task::Poll; use futures_util::future::Fuse; use futures_util::future::FutureExt; -use self::mio_named_pipes::NamedPipe; -use self::winapi::shared::minwindef::*; -use self::winapi::shared::winerror::*; -use self::winapi::um::handleapi::*; -use self::winapi::um::processthreadsapi::*; -use self::winapi::um::synchapi::*; -use self::winapi::um::threadpoollegacyapiset::*; -use self::winapi::um::winbase::*; -use self::winapi::um::winnt::*; use super::SpawnedChild; +use mio_named_pipes::NamedPipe; use tokio_reactor::{Handle, PollEvented}; use tokio_sync::oneshot; +use winapi::shared::minwindef::*; +use winapi::shared::winerror::*; +use winapi::um::handleapi::*; +use winapi::um::processthreadsapi::*; +use winapi::um::synchapi::*; +use winapi::um::threadpoollegacyapiset::*; +use winapi::um::winbase::*; +use winapi::um::winnt::*; #[must_use = "futures do nothing unless polled"] pub struct Child { @@ -54,7 +51,7 @@ pub struct Child { } impl fmt::Debug for Child { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt.debug_struct("Child") .field("pid", &self.id()) .field("child", &self.child) @@ -104,7 +101,7 @@ impl Kill for Child { impl Future for Child { type Output = io::Result; - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let inner = Pin::get_mut(self); loop { if let Some(ref mut w) = inner.waiting { diff --git a/tokio-process/tests/issue_42.rs b/tokio-process/tests/issue_42.rs index 465f612bb..77deae7e9 100644 --- a/tokio-process/tests/issue_42.rs +++ b/tokio-process/tests/issue_42.rs @@ -1,18 +1,15 @@ #![cfg(unix)] - -extern crate tokio_process; +#![deny(warnings, rust_2018_idioms)] use futures_util::future::FutureExt; use futures_util::stream::FuturesOrdered; use futures_util::stream::StreamExt; -use std::future::Future; -use std::io; -use std::pin::Pin; -use std::process::{Command, ExitStatus, Stdio}; +use std::process::{Command, Stdio}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::thread; use std::time::Duration; +use tokio::runtime::current_thread; use tokio_process::CommandExt; mod support; @@ -22,8 +19,7 @@ fn run_test() { let finished_clone = finished.clone(); thread::spawn(move || { - let mut futures: FuturesOrdered>>>> = - FuturesOrdered::new(); + let mut futures = FuturesOrdered::new(); for i in 0..2 { futures.push( Command::new("echo") @@ -36,7 +32,10 @@ fn run_test() { .boxed(), ) } - support::run_with_timeout(futures.collect::>>()); + + let mut rt = current_thread::Runtime::new().expect("failed to get runtime"); + rt.block_on(support::with_timeout(futures.collect::>())); + drop(rt); finished_clone.store(true, Ordering::SeqCst); }); diff --git a/tokio-process/tests/smoke.rs b/tokio-process/tests/smoke.rs index 9d11002ad..d4d2189e1 100644 --- a/tokio-process/tests/smoke.rs +++ b/tokio-process/tests/smoke.rs @@ -1,11 +1,12 @@ -extern crate tokio_process; +#![deny(warnings, rust_2018_idioms)] +#![feature(async_await)] use tokio_process::CommandExt; mod support; -#[test] -fn simple() { +#[tokio::test] +async fn simple() { let mut cmd = support::cmd("exit"); cmd.arg("2"); @@ -14,7 +15,9 @@ fn simple() { let id = child.id(); assert!(id > 0); - let status = support::run_with_timeout(&mut child).expect("failed to run future"); + let status = support::with_timeout(&mut child) + .await + .expect("failed to run future"); assert_eq!(status.code(), Some(2)); assert_eq!(child.id(), id); diff --git a/tokio-process/tests/stdio.rs b/tokio-process/tests/stdio.rs index c442f0091..f26cbd8a7 100644 --- a/tokio-process/tests/stdio.rs +++ b/tokio-process/tests/stdio.rs @@ -1,21 +1,17 @@ +#![deny(warnings, rust_2018_idioms)] #![feature(async_await)] #[macro_use] extern crate log; -extern crate tokio_io; -extern crate tokio_process; -use std::future::Future; use std::io; -use std::pin::Pin; use std::process::{Command, ExitStatus, Stdio}; use futures_util::future; use futures_util::future::FutureExt; -use futures_util::io::AsyncBufReadExt; -use futures_util::io::AsyncWriteExt; -use futures_util::io::BufReader; -use futures_util::stream::{self, StreamExt}; +use futures_util::stream::StreamExt; +use tokio::codec::{FramedRead, LinesCodec}; +use tokio::io::AsyncWriteExt; use tokio_process::{Child, CommandExt}; mod support; @@ -26,66 +22,66 @@ fn cat() -> Command { cmd } -fn feed_cat(mut cat: Child, n: usize) -> Pin>>> { - let stdin = cat.stdin().take().unwrap(); +async fn feed_cat(mut cat: Child, n: usize) -> io::Result { + let mut stdin = cat.stdin().take().unwrap(); let stdout = cat.stdout().take().unwrap(); - debug!("starting to feed"); // Produce n lines on the child's stdout. - let numbers = stream::iter(0..n); - let write = numbers - .fold(stdin, move |mut stdin, i| { - let fut = async move { - debug!("sending line {} to child", i); - let bytes = format!("line {}\n", i).into_bytes(); - AsyncWriteExt::write_all(&mut stdin, &bytes).await.unwrap(); - stdin - }; - fut - }) - .map(|_| ()); + let write = async { + debug!("starting to feed"); - // Try to read `n + 1` lines, ensuring the last one is empty - // (i.e. EOF is reached after `n` lines. - let reader = BufReader::new(stdout); - let expected_numbers = stream::iter(0..=n); - let read = expected_numbers.fold((reader, 0), move |(mut reader, i), _| { - let fut = async move { - let done = i >= n; + for i in 0..n { + debug!("sending line {} to child", i); + let bytes = format!("line {}\n", i).into_bytes(); + stdin.write_all(&bytes).await.unwrap(); + } + + drop(stdin); + }; + + let read = async { + let mut reader = FramedRead::new(stdout, LinesCodec::new()); + let mut num_lines = 0; + + // Try to read `n + 1` lines, ensuring the last one is empty + // (i.e. EOF is reached after `n` lines. + loop { debug!("starting read from child"); - let mut vec = Vec::new(); - AsyncBufReadExt::read_until(&mut reader, b'\n', &mut vec) + + let data = reader + .next() .await - .unwrap(); + .unwrap_or_else(|| Ok(String::new())) + .expect("failed to read line"); + + let num_read = data.len(); + let done = num_lines >= n; + debug!( "read line {} from child ({} bytes, done: {})", - i, - vec.len(), - done + num_lines, num_read, done ); - match (done, vec.len()) { - (false, 0) => { - panic!("broken pipe"); - } - (true, n) if n != 0 => { - panic!("extraneous data"); - } + + match (done, num_read) { + (false, 0) => panic!("broken pipe"), + (true, n) if n != 0 => panic!("extraneous data"), _ => { - let s = std::str::from_utf8(&vec).unwrap(); - let expected = format!("line {}\n", i); - if done || s == expected { - (reader, i + 1) - } else { - panic!("unexpected data"); - } + let expected = format!("line {}", num_lines); + assert_eq!(expected, data); } + }; + + num_lines += 1; + if num_lines >= n { + break; } - }; - fut - }); + } + }; // Compose reading and writing concurrently. - future::join(write, read).then(|_| cat).boxed() + future::join3(write, read, cat) + .map(|(_, _, status)| status) + .await } /// Check for the following properties when feeding stdin and @@ -99,42 +95,42 @@ fn feed_cat(mut cat: Child, n: usize) -> Pin Command { let mut me = env::current_exe().unwrap(); @@ -29,14 +27,3 @@ pub fn with_timeout(future: F) -> impl Future { future::ready(r.unwrap()) }) } - -pub fn run_with_timeout(future: F) -> F::Output -where - F: Future, -{ - // NB: Timeout requires a timer registration which is provided by - // tokio's `current_thread::Runtime`, but isn't available by just using - // tokio's default CurrentThread executor which powers `current_thread::block_on_all`. - let mut rt = CurrentThreadRuntime::new().expect("failed to get runtime"); - rt.block_on(with_timeout(future)) -}