From 227533d456fe32e48ffcd3796f1e6c8f9318b230 Mon Sep 17 00:00:00 2001 From: Carl Lerche Date: Fri, 25 Oct 2019 12:50:15 -0700 Subject: net: move into tokio crate (#1683) A step towards collapsing Tokio sub crates into a single `tokio` crate (#1318). The `net` implementation is now provided by the main `tokio` crate. Functionality can be opted out of by using the various net related feature flags. --- tests-integration/Cargo.toml | 14 ++++ tests-integration/README.md | 1 + tests-integration/src/bin/test-cat.rs | 20 +++++ tests-integration/tests/process_stdio.rs | 127 +++++++++++++++++++++++++++++++ 4 files changed, 162 insertions(+) create mode 100644 tests-integration/Cargo.toml create mode 100644 tests-integration/README.md create mode 100644 tests-integration/src/bin/test-cat.rs create mode 100644 tests-integration/tests/process_stdio.rs (limited to 'tests-integration') diff --git a/tests-integration/Cargo.toml b/tests-integration/Cargo.toml new file mode 100644 index 00000000..0dcc1e81 --- /dev/null +++ b/tests-integration/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "tests-integration" +version = "0.1.0" +authors = ["Tokio Contributors "] +edition = "2018" +publish = false + +[dependencies] + +[dev-dependencies] +tokio = { path = "../tokio" } +tokio-test = { path = "../tokio-test" } + +futures-preview = { version = "=0.3.0-alpha.19", features = ["async-await"] } diff --git a/tests-integration/README.md b/tests-integration/README.md new file mode 100644 index 00000000..955472d0 --- /dev/null +++ b/tests-integration/README.md @@ -0,0 +1 @@ +Tests that require additional components than just the `tokio` crate. diff --git a/tests-integration/src/bin/test-cat.rs b/tests-integration/src/bin/test-cat.rs new file mode 100644 index 00000000..3b30bf18 --- /dev/null +++ b/tests-integration/src/bin/test-cat.rs @@ -0,0 +1,20 @@ +//! A cat-like utility that can be used as a subprocess to test I/O +//! stream communication. + +use std::io; +use std::io::Write; + +fn main() { + let stdin = io::stdin(); + let mut stdout = io::stdout(); + let mut line = String::new(); + loop { + line.clear(); + stdin.read_line(&mut line).unwrap(); + if line.is_empty() { + break; + } + stdout.write_all(line.as_bytes()).unwrap(); + } + stdout.flush().unwrap(); +} diff --git a/tests-integration/tests/process_stdio.rs b/tests-integration/tests/process_stdio.rs new file mode 100644 index 00000000..7e4f3d3e --- /dev/null +++ b/tests-integration/tests/process_stdio.rs @@ -0,0 +1,127 @@ +#![warn(rust_2018_idioms)] + +use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; +use tokio::process::{Child, Command}; +use tokio_test::assert_ok; + +use futures::future::{self, FutureExt}; +use futures::stream::StreamExt; +use std::env; +use std::io; +use std::process::{ExitStatus, Stdio}; + +fn cat() -> Command { + let mut me = env::current_exe().unwrap(); + me.pop(); + + if me.ends_with("deps") { + me.pop(); + } + + me.push("test-cat"); + + let mut cmd = Command::new(me); + cmd.stdin(Stdio::piped()).stdout(Stdio::piped()); + cmd +} + +async fn feed_cat(mut cat: Child, n: usize) -> io::Result { + let mut stdin = cat.stdin().take().unwrap(); + let stdout = cat.stdout().take().unwrap(); + + // Produce n lines on the child's stdout. + let write = async { + for i in 0..n { + let bytes = format!("line {}\n", i).into_bytes(); + stdin.write_all(&bytes).await.unwrap(); + } + + drop(stdin); + }; + + let read = async { + let mut reader = BufReader::new(stdout).lines(); + 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 { + let data = reader + .next() + .await + .unwrap_or_else(|| Ok(String::new())) + .expect("failed to read line"); + + let num_read = data.len(); + let done = num_lines >= n; + + match (done, num_read) { + (false, 0) => panic!("broken pipe"), + (true, n) if n != 0 => panic!("extraneous data"), + _ => { + let expected = format!("line {}", num_lines); + assert_eq!(expected, data); + } + }; + + num_lines += 1; + if num_lines >= n { + break; + } + } + }; + + // Compose reading and writing concurrently. + future::join3(write, read, cat) + .map(|(_, _, status)| status) + .await +} + +/// Check for the following properties when feeding stdin and +/// consuming stdout of a cat-like process: +/// +/// - A number of lines that amounts to a number of bytes exceeding a +/// typical OS buffer size can be fed to the child without +/// deadlock. This tests that we also consume the stdout +/// concurrently; otherwise this would deadlock. +/// +/// - We read the same lines from the child that we fed it. +/// +/// - The child does produce EOF on stdout after the last line. +#[tokio::test] +async fn feed_a_lot() { + let child = cat().spawn().unwrap(); + let status = feed_cat(child, 10000).await.unwrap(); + assert_eq!(status.code(), Some(0)); +} + +#[tokio::test] +async fn wait_with_output_captures() { + let mut child = cat().spawn().unwrap(); + let mut stdin = child.stdin().take().unwrap(); + + let write_bytes = b"1234"; + + let future = async { + stdin.write_all(write_bytes).await?; + drop(stdin); + let out = child.wait_with_output(); + out.await + }; + + let output = future.await.unwrap(); + + assert!(output.status.success()); + assert_eq!(output.stdout, write_bytes); + assert_eq!(output.stderr.len(), 0); +} + +#[tokio::test] +async fn status_closes_any_pipes() { + // Cat will open a pipe between the parent and child. + // If `status_async` doesn't ensure the handles are closed, + // we would end up blocking forever (and time out). + let child = cat().status(); + + assert_ok!(child.await); +} -- cgit v1.2.3