Move tests and support utilities to sub crates. (#268)
These crates will not be published to crates.io, but moving them allows `tower-h2` to also depend on the test utilities.
This commit is contained in:
		
							
								
								
									
										70
									
								
								tests/h2-support/src/assert.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								tests/h2-support/src/assert.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | ||||
|  | ||||
| #[macro_export] | ||||
| macro_rules! assert_closed { | ||||
|     ($transport:expr) => {{ | ||||
|         assert_eq!($transport.poll().unwrap(), None.into()); | ||||
|     }} | ||||
| } | ||||
|  | ||||
| #[macro_export] | ||||
| macro_rules! assert_headers { | ||||
|     ($frame:expr) => {{ | ||||
|         match $frame { | ||||
|             ::h2::frame::Frame::Headers(v) => v, | ||||
|             f => panic!("expected HEADERS; actual={:?}", f), | ||||
|         } | ||||
|     }} | ||||
| } | ||||
|  | ||||
| #[macro_export] | ||||
| macro_rules! assert_data { | ||||
|     ($frame:expr) => {{ | ||||
|         match $frame { | ||||
|             ::h2::frame::Frame::Data(v) => v, | ||||
|             f => panic!("expected DATA; actual={:?}", f), | ||||
|         } | ||||
|     }} | ||||
| } | ||||
|  | ||||
| #[macro_export] | ||||
| macro_rules! assert_ping { | ||||
|     ($frame:expr) => {{ | ||||
|         match $frame { | ||||
|             ::h2::frame::Frame::Ping(v) => v, | ||||
|             f => panic!("expected PING; actual={:?}", f), | ||||
|         } | ||||
|     }} | ||||
| } | ||||
|  | ||||
| #[macro_export] | ||||
| macro_rules! assert_settings { | ||||
|     ($frame:expr) => {{ | ||||
|         match $frame { | ||||
|             ::h2::frame::Frame::Settings(v) => v, | ||||
|             f => panic!("expected SETTINGS; actual={:?}", f), | ||||
|         } | ||||
|     }} | ||||
| } | ||||
|  | ||||
| #[macro_export] | ||||
| macro_rules! poll_err { | ||||
|     ($transport:expr) => {{ | ||||
|         match $transport.poll() { | ||||
|             Err(e) => e, | ||||
|             frame => panic!("expected error; actual={:?}", frame), | ||||
|         } | ||||
|     }} | ||||
| } | ||||
|  | ||||
| #[macro_export] | ||||
| macro_rules! poll_data { | ||||
|     ($transport:expr) => {{ | ||||
|         use h2::frame::Frame; | ||||
|         use futures::Async; | ||||
|  | ||||
|         match $transport.poll() { | ||||
|             Ok(Async::Ready(Some(Frame::Data(frame)))) => frame, | ||||
|             frame => panic!("expected data frame; actual={:?}", frame), | ||||
|         } | ||||
|     }} | ||||
| } | ||||
							
								
								
									
										363
									
								
								tests/h2-support/src/frames.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										363
									
								
								tests/h2-support/src/frames.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,363 @@ | ||||
| use std::fmt; | ||||
|  | ||||
| use bytes::{Bytes, IntoBuf}; | ||||
| use http::{self, HeaderMap, HttpTryFrom}; | ||||
|  | ||||
| use super::SendFrame; | ||||
| use h2::frame::{self, Frame, StreamId}; | ||||
|  | ||||
| pub const SETTINGS: &'static [u8] = &[0, 0, 0, 4, 0, 0, 0, 0, 0]; | ||||
| pub const SETTINGS_ACK: &'static [u8] = &[0, 0, 0, 4, 1, 0, 0, 0, 0]; | ||||
|  | ||||
| // ==== helper functions to easily construct h2 Frames ==== | ||||
|  | ||||
| pub fn headers<T>(id: T) -> Mock<frame::Headers> | ||||
| where | ||||
|     T: Into<StreamId>, | ||||
| { | ||||
|     Mock(frame::Headers::new( | ||||
|         id.into(), | ||||
|         frame::Pseudo::default(), | ||||
|         HeaderMap::default(), | ||||
|     )) | ||||
| } | ||||
|  | ||||
| pub fn data<T, B>(id: T, buf: B) -> Mock<frame::Data> | ||||
| where | ||||
|     T: Into<StreamId>, | ||||
|     B: Into<Bytes>, | ||||
| { | ||||
|     Mock(frame::Data::new(id.into(), buf.into())) | ||||
| } | ||||
|  | ||||
| pub fn push_promise<T1, T2>(id: T1, promised: T2) -> Mock<frame::PushPromise> | ||||
| where | ||||
|     T1: Into<StreamId>, | ||||
|     T2: Into<StreamId>, | ||||
| { | ||||
|     Mock(frame::PushPromise::new( | ||||
|         id.into(), | ||||
|         promised.into(), | ||||
|         frame::Pseudo::default(), | ||||
|         HeaderMap::default(), | ||||
|     )) | ||||
| } | ||||
|  | ||||
| pub fn window_update<T>(id: T, sz: u32) -> frame::WindowUpdate | ||||
| where | ||||
|     T: Into<StreamId>, | ||||
| { | ||||
|     frame::WindowUpdate::new(id.into(), sz) | ||||
| } | ||||
|  | ||||
| pub fn go_away<T>(id: T) -> Mock<frame::GoAway> | ||||
| where | ||||
|     T: Into<StreamId>, | ||||
| { | ||||
|     Mock(frame::GoAway::new(id.into(), frame::Reason::NO_ERROR)) | ||||
| } | ||||
|  | ||||
| pub fn reset<T>(id: T) -> Mock<frame::Reset> | ||||
| where | ||||
|     T: Into<StreamId>, | ||||
| { | ||||
|     Mock(frame::Reset::new(id.into(), frame::Reason::NO_ERROR)) | ||||
| } | ||||
|  | ||||
| pub fn settings() -> Mock<frame::Settings> { | ||||
|     Mock(frame::Settings::default()) | ||||
| } | ||||
|  | ||||
| pub fn settings_ack() -> Mock<frame::Settings> { | ||||
|     Mock(frame::Settings::ack()) | ||||
| } | ||||
|  | ||||
| pub fn ping(payload: [u8; 8]) -> Mock<frame::Ping> { | ||||
|     Mock(frame::Ping::new(payload)) | ||||
| } | ||||
|  | ||||
| // === Generic helpers of all frame types | ||||
|  | ||||
| pub struct Mock<T>(T); | ||||
|  | ||||
| impl<T: fmt::Debug> fmt::Debug for Mock<T> { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
|         fmt::Debug::fmt(&self.0, f) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<T> From<Mock<T>> for Frame | ||||
| where | ||||
|     T: Into<Frame>, | ||||
| { | ||||
|     fn from(src: Mock<T>) -> Self { | ||||
|         src.0.into() | ||||
|     } | ||||
| } | ||||
|  | ||||
| // Headers helpers | ||||
|  | ||||
| impl Mock<frame::Headers> { | ||||
|     pub fn request<M, U>(self, method: M, uri: U) -> Self | ||||
|     where | ||||
|         M: HttpTryInto<http::Method>, | ||||
|         U: HttpTryInto<http::Uri>, | ||||
|     { | ||||
|         let method = method.try_into().unwrap(); | ||||
|         let uri = uri.try_into().unwrap(); | ||||
|         let (id, _, fields) = self.into_parts(); | ||||
|         let frame = frame::Headers::new(id, frame::Pseudo::request(method, uri), fields); | ||||
|         Mock(frame) | ||||
|     } | ||||
|  | ||||
|     pub fn response<S>(self, status: S) -> Self | ||||
|     where | ||||
|         S: HttpTryInto<http::StatusCode>, | ||||
|     { | ||||
|         let status = status.try_into().unwrap(); | ||||
|         let (id, _, fields) = self.into_parts(); | ||||
|         let frame = frame::Headers::new(id, frame::Pseudo::response(status), fields); | ||||
|         Mock(frame) | ||||
|     } | ||||
|  | ||||
|     pub fn fields(self, fields: HeaderMap) -> Self { | ||||
|         let (id, pseudo, _) = self.into_parts(); | ||||
|         let frame = frame::Headers::new(id, pseudo, fields); | ||||
|         Mock(frame) | ||||
|     } | ||||
|  | ||||
|     pub fn field<K, V>(self, key: K, value: V) -> Self | ||||
|     where | ||||
|         K: HttpTryInto<http::header::HeaderName>, | ||||
|         V: HttpTryInto<http::header::HeaderValue>, | ||||
|     { | ||||
|         let (id, pseudo, mut fields) = self.into_parts(); | ||||
|         fields.insert(key.try_into().unwrap(), value.try_into().unwrap()); | ||||
|         let frame = frame::Headers::new(id, pseudo, fields); | ||||
|         Mock(frame) | ||||
|     } | ||||
|  | ||||
|     pub fn scheme(self, value: &str) -> Self | ||||
|     { | ||||
|         let (id, mut pseudo, fields) = self.into_parts(); | ||||
|         let value = value.parse().unwrap(); | ||||
|  | ||||
|         pseudo.set_scheme(value); | ||||
|  | ||||
|         Mock(frame::Headers::new(id, pseudo, fields)) | ||||
|     } | ||||
|  | ||||
|     pub fn eos(mut self) -> Self { | ||||
|         self.0.set_end_stream(); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     pub fn into_fields(self) -> HeaderMap { | ||||
|         self.0.into_parts().1 | ||||
|     } | ||||
|  | ||||
|     fn into_parts(self) -> (StreamId, frame::Pseudo, HeaderMap) { | ||||
|         assert!(!self.0.is_end_stream(), "eos flag will be lost"); | ||||
|         assert!(self.0.is_end_headers(), "unset eoh will be lost"); | ||||
|         let id = self.0.stream_id(); | ||||
|         let parts = self.0.into_parts(); | ||||
|         (id, parts.0, parts.1) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<Mock<frame::Headers>> for frame::Headers { | ||||
|     fn from(src: Mock<frame::Headers>) -> Self { | ||||
|         src.0 | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<Mock<frame::Headers>> for SendFrame { | ||||
|     fn from(src: Mock<frame::Headers>) -> Self { | ||||
|         Frame::Headers(src.0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| // Data helpers | ||||
|  | ||||
| impl Mock<frame::Data> { | ||||
|     pub fn eos(mut self) -> Self { | ||||
|         self.0.set_end_stream(true); | ||||
|         self | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<Mock<frame::Data>> for SendFrame { | ||||
|     fn from(src: Mock<frame::Data>) -> Self { | ||||
|         let id = src.0.stream_id(); | ||||
|         let eos = src.0.is_end_stream(); | ||||
|         let payload = src.0.into_payload(); | ||||
|         let mut frame = frame::Data::new(id, payload.into_buf()); | ||||
|         frame.set_end_stream(eos); | ||||
|         Frame::Data(frame) | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| // PushPromise helpers | ||||
|  | ||||
| impl Mock<frame::PushPromise> { | ||||
|     pub fn request<M, U>(self, method: M, uri: U) -> Self | ||||
|     where | ||||
|         M: HttpTryInto<http::Method>, | ||||
|         U: HttpTryInto<http::Uri>, | ||||
|     { | ||||
|         let method = method.try_into().unwrap(); | ||||
|         let uri = uri.try_into().unwrap(); | ||||
|         let (id, promised, _, fields) = self.into_parts(); | ||||
|         let frame = | ||||
|             frame::PushPromise::new(id, promised, frame::Pseudo::request(method, uri), fields); | ||||
|         Mock(frame) | ||||
|     } | ||||
|  | ||||
|     pub fn fields(self, fields: HeaderMap) -> Self { | ||||
|         let (id, promised, pseudo, _) = self.into_parts(); | ||||
|         let frame = frame::PushPromise::new(id, promised, pseudo, fields); | ||||
|         Mock(frame) | ||||
|     } | ||||
|  | ||||
|     fn into_parts(self) -> (StreamId, StreamId, frame::Pseudo, HeaderMap) { | ||||
|         assert!(self.0.is_end_headers(), "unset eoh will be lost"); | ||||
|         let id = self.0.stream_id(); | ||||
|         let promised = self.0.promised_id(); | ||||
|         let parts = self.0.into_parts(); | ||||
|         (id, promised, parts.0, parts.1) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<Mock<frame::PushPromise>> for SendFrame { | ||||
|     fn from(src: Mock<frame::PushPromise>) -> Self { | ||||
|         Frame::PushPromise(src.0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| // GoAway helpers | ||||
|  | ||||
| impl Mock<frame::GoAway> { | ||||
|     pub fn protocol_error(self) -> Self { | ||||
|         Mock(frame::GoAway::new( | ||||
|             self.0.last_stream_id(), | ||||
|             frame::Reason::PROTOCOL_ERROR, | ||||
|         )) | ||||
|     } | ||||
|  | ||||
|     pub fn flow_control(self) -> Self { | ||||
|         Mock(frame::GoAway::new( | ||||
|             self.0.last_stream_id(), | ||||
|             frame::Reason::FLOW_CONTROL_ERROR, | ||||
|         )) | ||||
|     } | ||||
|  | ||||
|     pub fn frame_size(self) -> Self { | ||||
|         Mock(frame::GoAway::new( | ||||
|             self.0.last_stream_id(), | ||||
|             frame::Reason::FRAME_SIZE_ERROR, | ||||
|         )) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<Mock<frame::GoAway>> for SendFrame { | ||||
|     fn from(src: Mock<frame::GoAway>) -> Self { | ||||
|         Frame::GoAway(src.0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| // ==== Reset helpers | ||||
|  | ||||
| impl Mock<frame::Reset> { | ||||
|     pub fn protocol_error(self) -> Self { | ||||
|         let id = self.0.stream_id(); | ||||
|         Mock(frame::Reset::new(id, frame::Reason::PROTOCOL_ERROR)) | ||||
|     } | ||||
|  | ||||
|     pub fn flow_control(self) -> Self { | ||||
|         let id = self.0.stream_id(); | ||||
|         Mock(frame::Reset::new(id, frame::Reason::FLOW_CONTROL_ERROR)) | ||||
|     } | ||||
|  | ||||
|     pub fn refused(self) -> Self { | ||||
|         let id = self.0.stream_id(); | ||||
|         Mock(frame::Reset::new(id, frame::Reason::REFUSED_STREAM)) | ||||
|     } | ||||
|  | ||||
|     pub fn cancel(self) -> Self { | ||||
|         let id = self.0.stream_id(); | ||||
|         Mock(frame::Reset::new(id, frame::Reason::CANCEL)) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<Mock<frame::Reset>> for SendFrame { | ||||
|     fn from(src: Mock<frame::Reset>) -> Self { | ||||
|         Frame::Reset(src.0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| // ==== Settings helpers | ||||
|  | ||||
| impl Mock<frame::Settings> { | ||||
|     pub fn max_concurrent_streams(mut self, max: u32) -> Self { | ||||
|         self.0.set_max_concurrent_streams(Some(max)); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     pub fn initial_window_size(mut self, val: u32) -> Self { | ||||
|         self.0.set_initial_window_size(Some(val)); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     pub fn max_header_list_size(mut self, val: u32) -> Self { | ||||
|         self.0.set_max_header_list_size(Some(val)); | ||||
|         self | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<Mock<frame::Settings>> for frame::Settings { | ||||
|     fn from(src: Mock<frame::Settings>) -> Self { | ||||
|         src.0 | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<Mock<frame::Settings>> for SendFrame { | ||||
|     fn from(src: Mock<frame::Settings>) -> Self { | ||||
|         Frame::Settings(src.0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| // ==== Ping helpers | ||||
|  | ||||
| impl Mock<frame::Ping> { | ||||
|     pub fn pong(self) -> Self { | ||||
|         let payload = self.0.into_payload(); | ||||
|         Mock(frame::Ping::pong(payload)) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<Mock<frame::Ping>> for SendFrame { | ||||
|     fn from(src: Mock<frame::Ping>) -> Self { | ||||
|         Frame::Ping(src.0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| // ==== "trait alias" for types that are HttpTryFrom and have Debug Errors ==== | ||||
|  | ||||
| pub trait HttpTryInto<T> { | ||||
|     type Error: fmt::Debug; | ||||
|  | ||||
|     fn try_into(self) -> Result<T, Self::Error>; | ||||
| } | ||||
|  | ||||
| impl<T, U> HttpTryInto<T> for U | ||||
| where | ||||
|     T: HttpTryFrom<U>, | ||||
|     T::Error: fmt::Debug, | ||||
| { | ||||
|     type Error = T::Error; | ||||
|  | ||||
|     fn try_into(self) -> Result<T, Self::Error> { | ||||
|         T::try_from(self) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										242
									
								
								tests/h2-support/src/future_ext.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										242
									
								
								tests/h2-support/src/future_ext.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,242 @@ | ||||
| use futures::{Async, Future, Poll}; | ||||
|  | ||||
| use std::fmt; | ||||
|  | ||||
| /// Future extension helpers that are useful for tests | ||||
| pub trait FutureExt: Future { | ||||
|     /// Panic on error | ||||
|     fn unwrap(self) -> Unwrap<Self> | ||||
|     where | ||||
|         Self: Sized, | ||||
|         Self::Error: fmt::Debug, | ||||
|     { | ||||
|         Unwrap { | ||||
|             inner: self, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Panic on success, yielding the content of an `Err`. | ||||
|     fn unwrap_err(self) -> UnwrapErr<Self> | ||||
|     where | ||||
|         Self: Sized, | ||||
|         Self::Error: fmt::Debug, | ||||
|     { | ||||
|         UnwrapErr { | ||||
|             inner: self, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Panic on success, with a message. | ||||
|     fn expect_err<T>(self, msg: T) -> ExpectErr<Self> | ||||
|     where | ||||
|         Self: Sized, | ||||
|         Self::Error: fmt::Debug, | ||||
|         T: fmt::Display, | ||||
|     { | ||||
|         ExpectErr{ | ||||
|             inner: self, | ||||
|             msg: msg.to_string(), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Panic on error, with a message. | ||||
|     fn expect<T>(self, msg: T) -> Expect<Self> | ||||
|     where | ||||
|         Self: Sized, | ||||
|         Self::Error: fmt::Debug, | ||||
|         T: fmt::Display, | ||||
|     { | ||||
|         Expect { | ||||
|             inner: self, | ||||
|             msg: msg.to_string(), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Drive `other` by polling `self`. | ||||
|     /// | ||||
|     /// `self` must not resolve before `other` does. | ||||
|     fn drive<T>(self, other: T) -> Drive<Self, T> | ||||
|     where | ||||
|         T: Future, | ||||
|         T::Error: fmt::Debug, | ||||
|         Self: Future<Item = ()> + Sized, | ||||
|         Self::Error: fmt::Debug, | ||||
|     { | ||||
|         Drive { | ||||
|             driver: Some(self), | ||||
|             future: other, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Wrap this future in one that will yield NotReady once before continuing. | ||||
|     /// | ||||
|     /// This allows the executor to poll other futures before trying this one | ||||
|     /// again. | ||||
|     fn yield_once(self) -> Box<Future<Item = Self::Item, Error = Self::Error>> | ||||
|     where | ||||
|         Self: Future + Sized + 'static, | ||||
|     { | ||||
|         let mut ready = false; | ||||
|         Box::new(::futures::future::poll_fn(move || { | ||||
|             if ready { | ||||
|                 Ok::<_, ()>(().into()) | ||||
|             } else { | ||||
|                 ready = true; | ||||
|                 ::futures::task::current().notify(); | ||||
|                 Ok(::futures::Async::NotReady) | ||||
|             } | ||||
|         }).then(|_| self)) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<T: Future> FutureExt for T {} | ||||
|  | ||||
| // ===== Unwrap ====== | ||||
|  | ||||
| /// Panic on error | ||||
| pub struct Unwrap<T> { | ||||
|     inner: T, | ||||
| } | ||||
|  | ||||
| impl<T> Future for Unwrap<T> | ||||
| where | ||||
|     T: Future, | ||||
|     T::Item: fmt::Debug, | ||||
|     T::Error: fmt::Debug, | ||||
| { | ||||
|     type Item = T::Item; | ||||
|     type Error = (); | ||||
|  | ||||
|     fn poll(&mut self) -> Poll<T::Item, ()> { | ||||
|         Ok(self.inner.poll().unwrap()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| // ===== UnwrapErr ====== | ||||
|  | ||||
| /// Panic on success. | ||||
| pub struct UnwrapErr<T> { | ||||
|     inner: T, | ||||
| } | ||||
|  | ||||
| impl<T> Future for UnwrapErr<T> | ||||
| where | ||||
|     T: Future, | ||||
|     T::Item: fmt::Debug, | ||||
|     T::Error: fmt::Debug, | ||||
| { | ||||
|     type Item = T::Error; | ||||
|     type Error = (); | ||||
|  | ||||
|     fn poll(&mut self) -> Poll<T::Error, ()> { | ||||
|         match self.inner.poll() { | ||||
|             Ok(Async::Ready(v)) => panic!("Future::unwrap_err() on an Ok value: {:?}", v), | ||||
|             Ok(Async::NotReady) => Ok(Async::NotReady), | ||||
|             Err(e) => Ok(Async::Ready(e)), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| // ===== Expect ====== | ||||
|  | ||||
| /// Panic on error | ||||
| pub struct Expect<T> { | ||||
|     inner: T, | ||||
|     msg: String, | ||||
| } | ||||
|  | ||||
| impl<T> Future for Expect<T> | ||||
| where | ||||
|     T: Future, | ||||
|     T::Item: fmt::Debug, | ||||
|     T::Error: fmt::Debug, | ||||
| { | ||||
|     type Item = T::Item; | ||||
|     type Error = (); | ||||
|  | ||||
|     fn poll(&mut self) -> Poll<T::Item, ()> { | ||||
|         Ok(self.inner.poll().expect(&self.msg)) | ||||
|     } | ||||
| } | ||||
|  | ||||
| // ===== ExpectErr ====== | ||||
|  | ||||
| /// Panic on success | ||||
| pub struct ExpectErr<T> { | ||||
|     inner: T, | ||||
|     msg: String, | ||||
| } | ||||
|  | ||||
| impl<T> Future for ExpectErr<T> | ||||
| where | ||||
|     T: Future, | ||||
|     T::Item: fmt::Debug, | ||||
|     T::Error: fmt::Debug, | ||||
| { | ||||
|     type Item = T::Error; | ||||
|     type Error = (); | ||||
|  | ||||
|     fn poll(&mut self) -> Poll<T::Error, ()> { | ||||
|         match self.inner.poll() { | ||||
|             Ok(Async::Ready(v)) => panic!("{}: {:?}", self.msg, v), | ||||
|             Ok(Async::NotReady) => Ok(Async::NotReady), | ||||
|             Err(e) => Ok(Async::Ready(e)), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| // ===== Drive ====== | ||||
|  | ||||
| /// Drive a future to completion while also polling the driver | ||||
| /// | ||||
| /// This is useful for H2 futures that also require the connection to be polled. | ||||
| pub struct Drive<T, U> { | ||||
|     driver: Option<T>, | ||||
|     future: U, | ||||
| } | ||||
|  | ||||
| impl<T, U> Future for Drive<T, U> | ||||
| where | ||||
|     T: Future<Item = ()>, | ||||
|     U: Future, | ||||
|     T::Error: fmt::Debug, | ||||
|     U::Error: fmt::Debug, | ||||
| { | ||||
|     type Item = (T, U::Item); | ||||
|     type Error = (); | ||||
|  | ||||
|     fn poll(&mut self) -> Poll<Self::Item, Self::Error> { | ||||
|         let mut looped = false; | ||||
|  | ||||
|         loop { | ||||
|             match self.future.poll() { | ||||
|                 Ok(Async::Ready(val)) => { | ||||
|                     // Get the driver | ||||
|                     let driver = self.driver.take().unwrap(); | ||||
|  | ||||
|                     return Ok((driver, val).into()); | ||||
|                 }, | ||||
|                 Ok(_) => {}, | ||||
|                 Err(e) => panic!("unexpected error; {:?}", e), | ||||
|             } | ||||
|  | ||||
|             match self.driver.as_mut().unwrap().poll() { | ||||
|                 Ok(Async::Ready(_)) => { | ||||
|                     if looped { | ||||
|                         // Try polling the future one last time | ||||
|                         panic!("driver resolved before future") | ||||
|                     } else { | ||||
|                         looped = true; | ||||
|                         continue; | ||||
|                     } | ||||
|                 }, | ||||
|                 Ok(Async::NotReady) => {}, | ||||
|                 Err(e) => panic!("unexpected error; {:?}", e), | ||||
|             } | ||||
|  | ||||
|             return Ok(Async::NotReady); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										36
									
								
								tests/h2-support/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								tests/h2-support/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| //! Utilities to support tests. | ||||
|  | ||||
| pub extern crate bytes; | ||||
| pub extern crate env_logger; | ||||
| #[macro_use] | ||||
| pub extern crate futures; | ||||
| pub extern crate h2; | ||||
| pub extern crate http; | ||||
| pub extern crate string; | ||||
| #[macro_use] | ||||
| pub extern crate tokio_io; | ||||
|  | ||||
| #[macro_use] | ||||
| mod assert; | ||||
|  | ||||
| pub mod raw; | ||||
|  | ||||
| pub mod frames; | ||||
| pub mod prelude; | ||||
| pub mod mock; | ||||
| pub mod mock_io; | ||||
| pub mod notify; | ||||
| pub mod util; | ||||
|  | ||||
| mod future_ext; | ||||
|  | ||||
| pub use future_ext::{FutureExt, Unwrap}; | ||||
|  | ||||
| pub type WindowSize = usize; | ||||
| pub const DEFAULT_WINDOW_SIZE: WindowSize = (1 << 16) - 1; | ||||
|  | ||||
| // This is our test Codec type | ||||
| pub type Codec<T> = h2::Codec<T, ::std::io::Cursor<::bytes::Bytes>>; | ||||
|  | ||||
| // This is the frame type that is sent | ||||
| pub type SendFrame = h2::frame::Frame<::std::io::Cursor<::bytes::Bytes>>; | ||||
							
								
								
									
										716
									
								
								tests/h2-support/src/mock.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										716
									
								
								tests/h2-support/src/mock.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,716 @@ | ||||
| use {frames, FutureExt, SendFrame}; | ||||
|  | ||||
| use h2::{self, RecvError, SendError}; | ||||
| use h2::frame::{self, Frame}; | ||||
|  | ||||
| use futures::{Async, Future, Poll, Stream}; | ||||
| use futures::sync::oneshot; | ||||
| use futures::task::{self, Task}; | ||||
|  | ||||
| use tokio_io::{AsyncRead, AsyncWrite}; | ||||
| use tokio_io::io::read_exact; | ||||
|  | ||||
| use std::{cmp, fmt, io, usize}; | ||||
| use std::io::ErrorKind::WouldBlock; | ||||
| use std::sync::{Arc, Mutex}; | ||||
|  | ||||
| /// A mock I/O | ||||
| #[derive(Debug)] | ||||
| pub struct Mock { | ||||
|     pipe: Pipe, | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| pub struct Handle { | ||||
|     codec: ::Codec<Pipe>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| pub struct Pipe { | ||||
|     inner: Arc<Mutex<Inner>>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| struct Inner { | ||||
|     /// Data written by the test case to the h2 lib. | ||||
|     rx: Vec<u8>, | ||||
|  | ||||
|     /// Notify when data is ready to be received. | ||||
|     rx_task: Option<Task>, | ||||
|  | ||||
|     /// Data written by the `h2` library to be read by the test case. | ||||
|     tx: Vec<u8>, | ||||
|  | ||||
|     /// Notify when data is written. This notifies the test case waiters. | ||||
|     tx_task: Option<Task>, | ||||
|  | ||||
|     /// Number of bytes that can be written before `write` returns `NotReady`. | ||||
|     tx_rem: usize, | ||||
|  | ||||
|     /// Task to notify when write capacity becomes available. | ||||
|     tx_rem_task: Option<Task>, | ||||
|  | ||||
|     /// True when the pipe is closed. | ||||
|     closed: bool, | ||||
| } | ||||
|  | ||||
| const PREFACE: &'static [u8] = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"; | ||||
|  | ||||
| /// Create a new mock and handle | ||||
| pub fn new() -> (Mock, Handle) { | ||||
|     new_with_write_capacity(usize::MAX) | ||||
| } | ||||
|  | ||||
| /// Create a new mock and handle allowing up to `cap` bytes to be written. | ||||
| pub fn new_with_write_capacity(cap: usize) -> (Mock, Handle) { | ||||
|     let inner = Arc::new(Mutex::new(Inner { | ||||
|         rx: vec![], | ||||
|         rx_task: None, | ||||
|         tx: vec![], | ||||
|         tx_task: None, | ||||
|         tx_rem: cap, | ||||
|         tx_rem_task: None, | ||||
|         closed: false, | ||||
|     })); | ||||
|  | ||||
|     let mock = Mock { | ||||
|         pipe: Pipe { | ||||
|             inner: inner.clone(), | ||||
|         }, | ||||
|     }; | ||||
|  | ||||
|     let handle = Handle { | ||||
|         codec: h2::Codec::new(Pipe { | ||||
|             inner, | ||||
|         }), | ||||
|     }; | ||||
|  | ||||
|     (mock, handle) | ||||
| } | ||||
|  | ||||
| // ===== impl Handle ===== | ||||
|  | ||||
| impl Handle { | ||||
|     /// Get a mutable reference to inner Codec. | ||||
|     pub fn codec_mut(&mut self) -> &mut ::Codec<Pipe> { | ||||
|         &mut self.codec | ||||
|     } | ||||
|  | ||||
|     /// Send a frame | ||||
|     pub fn send(&mut self, item: SendFrame) -> Result<(), SendError> { | ||||
|         // Queue the frame | ||||
|         self.codec.buffer(item).unwrap(); | ||||
|  | ||||
|         // Flush the frame | ||||
|         assert!(self.codec.flush()?.is_ready()); | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     /// Writes the client preface | ||||
|     pub fn write_preface(&mut self) { | ||||
|         use std::io::Write; | ||||
|  | ||||
|         // Write the connnection preface | ||||
|         self.codec.get_mut().write(PREFACE).unwrap(); | ||||
|     } | ||||
|  | ||||
|     /// Read the client preface | ||||
|     pub fn read_preface(self) -> Box<Future<Item = Self, Error = io::Error>> { | ||||
|         let buf = vec![0; PREFACE.len()]; | ||||
|         let ret = read_exact(self, buf).and_then(|(me, buf)| { | ||||
|             assert_eq!(buf, PREFACE); | ||||
|             Ok(me) | ||||
|         }); | ||||
|  | ||||
|         Box::new(ret) | ||||
|     } | ||||
|  | ||||
|     /// Perform the H2 handshake | ||||
|     pub fn assert_client_handshake( | ||||
|         self, | ||||
|     ) -> Box<Future<Item = (frame::Settings, Self), Error = h2::Error>> { | ||||
|         self.assert_client_handshake_with_settings(frame::Settings::default()) | ||||
|     } | ||||
|  | ||||
|     /// Perform the H2 handshake | ||||
|     pub fn assert_client_handshake_with_settings<T>( | ||||
|         mut self, | ||||
|         settings: T, | ||||
|     ) -> Box<Future<Item = (frame::Settings, Self), Error = h2::Error>> | ||||
|     where | ||||
|         T: Into<frame::Settings>, | ||||
|     { | ||||
|         let settings = settings.into(); | ||||
|         // Send a settings frame | ||||
|         self.send(settings.into()).unwrap(); | ||||
|  | ||||
|         let ret = self.read_preface() | ||||
|             .unwrap() | ||||
|             .and_then(|me| me.into_future().unwrap()) | ||||
|             .map(|(frame, mut me)| { | ||||
|                 match frame { | ||||
|                     Some(Frame::Settings(settings)) => { | ||||
|                         // Send the ACK | ||||
|                         let ack = frame::Settings::ack(); | ||||
|  | ||||
|                         // TODO: Don't unwrap? | ||||
|                         me.send(ack.into()).unwrap(); | ||||
|  | ||||
|                         (settings, me) | ||||
|                     }, | ||||
|                     Some(frame) => { | ||||
|                         panic!("unexpected frame; frame={:?}", frame); | ||||
|                     }, | ||||
|                     None => { | ||||
|                         panic!("unexpected EOF"); | ||||
|                     }, | ||||
|                 } | ||||
|             }) | ||||
|             .then(|res| { | ||||
|                 let (settings, me) = res.unwrap(); | ||||
|  | ||||
|                 me.into_future() | ||||
|                     .map_err(|_| unreachable!("all previous futures unwrapped")) | ||||
|                     .map(|(frame, me)| { | ||||
|                         let f = assert_settings!(frame.unwrap()); | ||||
|  | ||||
|                         // Is ACK | ||||
|                         assert!(f.is_ack()); | ||||
|  | ||||
|                         (settings, me) | ||||
|                     }) | ||||
|             }); | ||||
|  | ||||
|         Box::new(ret) | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /// Perform the H2 handshake | ||||
|     pub fn assert_server_handshake( | ||||
|         self, | ||||
|     ) -> Box<Future<Item = (frame::Settings, Self), Error = h2::Error>> { | ||||
|         self.assert_server_handshake_with_settings(frame::Settings::default()) | ||||
|     } | ||||
|  | ||||
|     /// Perform the H2 handshake | ||||
|     pub fn assert_server_handshake_with_settings<T>( | ||||
|         mut self, | ||||
|         settings: T, | ||||
|     ) -> Box<Future<Item = (frame::Settings, Self), Error = h2::Error>> | ||||
|     where | ||||
|         T: Into<frame::Settings>, | ||||
|     { | ||||
|         self.write_preface(); | ||||
|  | ||||
|         let settings = settings.into(); | ||||
|         self.send(settings.into()).unwrap(); | ||||
|  | ||||
|         let ret = self.into_future() | ||||
|             .unwrap() | ||||
|             .map(|(frame, mut me)| { | ||||
|                 match frame { | ||||
|                     Some(Frame::Settings(settings)) => { | ||||
|                         // Send the ACK | ||||
|                         let ack = frame::Settings::ack(); | ||||
|  | ||||
|                         // TODO: Don't unwrap? | ||||
|                         me.send(ack.into()).unwrap(); | ||||
|  | ||||
|                         (settings, me) | ||||
|                     }, | ||||
|                     Some(frame) => { | ||||
|                         panic!("unexpected frame; frame={:?}", frame); | ||||
|                     }, | ||||
|                     None => { | ||||
|                         panic!("unexpected EOF"); | ||||
|                     }, | ||||
|                 } | ||||
|             }) | ||||
|             .then(|res| { | ||||
|                 let (settings, me) = res.unwrap(); | ||||
|  | ||||
|                 me.into_future() | ||||
|                     .map_err(|e| panic!("error: {:?}", e)) | ||||
|                     .map(|(frame, me)| { | ||||
|                         let f = assert_settings!(frame.unwrap()); | ||||
|  | ||||
|                         // Is ACK | ||||
|                         assert!(f.is_ack()); | ||||
|  | ||||
|                         (settings, me) | ||||
|                     }) | ||||
|             }); | ||||
|  | ||||
|         Box::new(ret) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Stream for Handle { | ||||
|     type Item = Frame; | ||||
|     type Error = RecvError; | ||||
|  | ||||
|     fn poll(&mut self) -> Poll<Option<Self::Item>, RecvError> { | ||||
|         self.codec.poll() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl io::Read for Handle { | ||||
|     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { | ||||
|         self.codec.get_mut().read(buf) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl AsyncRead for Handle {} | ||||
|  | ||||
| impl io::Write for Handle { | ||||
|     fn write(&mut self, buf: &[u8]) -> io::Result<usize> { | ||||
|         self.codec.get_mut().write(buf) | ||||
|     } | ||||
|  | ||||
|     fn flush(&mut self) -> io::Result<()> { | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl AsyncWrite for Handle { | ||||
|     fn shutdown(&mut self) -> Poll<(), io::Error> { | ||||
|         use std::io::Write; | ||||
|         try_nb!(self.flush()); | ||||
|         Ok(().into()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Drop for Handle { | ||||
|     fn drop(&mut self) { | ||||
|         assert!(self.codec.shutdown().unwrap().is_ready()); | ||||
|  | ||||
|         let mut me = self.codec.get_mut().inner.lock().unwrap(); | ||||
|         me.closed = true; | ||||
|  | ||||
|         if let Some(task) = me.rx_task.take() { | ||||
|             task.notify(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| // ===== impl Mock ===== | ||||
|  | ||||
| impl io::Read for Mock { | ||||
|     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { | ||||
|         assert!( | ||||
|             buf.len() > 0, | ||||
|             "attempted read with zero length buffer... wut?" | ||||
|         ); | ||||
|  | ||||
|         let mut me = self.pipe.inner.lock().unwrap(); | ||||
|  | ||||
|         if me.rx.is_empty() { | ||||
|             if me.closed { | ||||
|                 return Ok(0); | ||||
|             } | ||||
|  | ||||
|             me.rx_task = Some(task::current()); | ||||
|             return Err(WouldBlock.into()); | ||||
|         } | ||||
|  | ||||
|         let n = cmp::min(buf.len(), me.rx.len()); | ||||
|         buf[..n].copy_from_slice(&me.rx[..n]); | ||||
|         me.rx.drain(..n); | ||||
|  | ||||
|         Ok(n) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl AsyncRead for Mock {} | ||||
|  | ||||
| impl io::Write for Mock { | ||||
|     fn write(&mut self, mut buf: &[u8]) -> io::Result<usize> { | ||||
|         let mut me = self.pipe.inner.lock().unwrap(); | ||||
|  | ||||
|         if me.closed { | ||||
|             return Err(io::Error::new(io::ErrorKind::BrokenPipe, "mock closed")); | ||||
|         } | ||||
|  | ||||
|         if me.tx_rem == 0 { | ||||
|             me.tx_rem_task = Some(task::current()); | ||||
|             return Err(io::ErrorKind::WouldBlock.into()); | ||||
|         } | ||||
|  | ||||
|         if buf.len() > me.tx_rem { | ||||
|             buf = &buf[..me.tx_rem]; | ||||
|         } | ||||
|  | ||||
|         me.tx.extend(buf); | ||||
|         me.tx_rem -= buf.len(); | ||||
|  | ||||
|         if let Some(task) = me.tx_task.take() { | ||||
|             task.notify(); | ||||
|         } | ||||
|  | ||||
|         Ok(buf.len()) | ||||
|     } | ||||
|  | ||||
|     fn flush(&mut self) -> io::Result<()> { | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl AsyncWrite for Mock { | ||||
|     fn shutdown(&mut self) -> Poll<(), io::Error> { | ||||
|         use std::io::Write; | ||||
|         try_nb!(self.flush()); | ||||
|         Ok(().into()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| // ===== impl Pipe ===== | ||||
|  | ||||
| impl io::Read for Pipe { | ||||
|     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { | ||||
|         assert!( | ||||
|             buf.len() > 0, | ||||
|             "attempted read with zero length buffer... wut?" | ||||
|         ); | ||||
|  | ||||
|         let mut me = self.inner.lock().unwrap(); | ||||
|  | ||||
|         if me.tx.is_empty() { | ||||
|             me.tx_task = Some(task::current()); | ||||
|             return Err(WouldBlock.into()); | ||||
|         } | ||||
|  | ||||
|         let n = cmp::min(buf.len(), me.tx.len()); | ||||
|         buf[..n].copy_from_slice(&me.tx[..n]); | ||||
|         me.tx.drain(..n); | ||||
|  | ||||
|         Ok(n) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl AsyncRead for Pipe {} | ||||
|  | ||||
| impl io::Write for Pipe { | ||||
|     fn write(&mut self, buf: &[u8]) -> io::Result<usize> { | ||||
|         let mut me = self.inner.lock().unwrap(); | ||||
|         me.rx.extend(buf); | ||||
|  | ||||
|         if let Some(task) = me.rx_task.take() { | ||||
|             task.notify(); | ||||
|         } | ||||
|  | ||||
|         Ok(buf.len()) | ||||
|     } | ||||
|  | ||||
|     fn flush(&mut self) -> io::Result<()> { | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl AsyncWrite for Pipe { | ||||
|     fn shutdown(&mut self) -> Poll<(), io::Error> { | ||||
|         use std::io::Write; | ||||
|         try_nb!(self.flush()); | ||||
|         Ok(().into()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub trait HandleFutureExt { | ||||
|     fn recv_settings(self) | ||||
|         -> RecvFrame<Box<Future<Item = (Option<Frame>, Handle), Error = ()>>> | ||||
|     where | ||||
|         Self: Sized + 'static, | ||||
|         Self: Future<Item = (frame::Settings, Handle)>, | ||||
|         Self::Error: fmt::Debug, | ||||
|     { | ||||
|         self.recv_custom_settings(frame::Settings::default()) | ||||
|     } | ||||
|  | ||||
|     fn recv_custom_settings<T>(self, settings: T) | ||||
|         -> RecvFrame<Box<Future<Item = (Option<Frame>, Handle), Error = ()>>> | ||||
|     where | ||||
|         Self: Sized + 'static, | ||||
|         Self: Future<Item = (frame::Settings, Handle)>, | ||||
|         Self::Error: fmt::Debug, | ||||
|         T: Into<frame::Settings>, | ||||
|     { | ||||
|         let map = self | ||||
|             .map(|(settings, handle)| (Some(settings.into()), handle)) | ||||
|             .unwrap(); | ||||
|  | ||||
|         let boxed: Box<Future<Item = (Option<Frame>, Handle), Error = ()>> = | ||||
|             Box::new(map); | ||||
|         RecvFrame { | ||||
|             inner: boxed, | ||||
|             frame: settings.into().into(), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn ignore_settings(self) -> Box<Future<Item = Handle, Error = ()>> | ||||
|     where | ||||
|         Self: Sized + 'static, | ||||
|         Self: Future<Item = (frame::Settings, Handle)>, | ||||
|         Self::Error: fmt::Debug, | ||||
|     { | ||||
|         Box::new(self.map(|(_settings, handle)| handle).unwrap()) | ||||
|     } | ||||
|  | ||||
|     fn recv_frame<T>(self, frame: T) -> RecvFrame<<Self as IntoRecvFrame>::Future> | ||||
|     where | ||||
|         Self: IntoRecvFrame + Sized, | ||||
|         T: Into<Frame>, | ||||
|     { | ||||
|         self.into_recv_frame(frame.into()) | ||||
|     } | ||||
|  | ||||
|     fn send_frame<T>(self, frame: T) -> SendFrameFut<Self> | ||||
|     where | ||||
|         Self: Sized, | ||||
|         T: Into<SendFrame>, | ||||
|     { | ||||
|         SendFrameFut { | ||||
|             inner: self, | ||||
|             frame: Some(frame.into()), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn ping_pong(self, payload: [u8; 8]) -> RecvFrame<<SendFrameFut<Self> as IntoRecvFrame>::Future> | ||||
|     where | ||||
|         Self: Future<Item=Handle> + Sized + 'static, | ||||
|         Self::Error: fmt::Debug, | ||||
|     { | ||||
|         self.send_frame(frames::ping(payload)) | ||||
|             .recv_frame(frames::ping(payload).pong()) | ||||
|     } | ||||
|  | ||||
|     fn idle_ms(self, ms: usize) -> Box<Future<Item = Handle, Error = Self::Error>> | ||||
|     where | ||||
|         Self: Sized + 'static, | ||||
|         Self: Future<Item = Handle>, | ||||
|         Self::Error: fmt::Debug, | ||||
|     { | ||||
|         use std::thread; | ||||
|         use std::time::Duration; | ||||
|  | ||||
|  | ||||
|         Box::new(self.and_then(move |handle| { | ||||
|             // This is terrible... but oh well | ||||
|             let (tx, rx) = oneshot::channel(); | ||||
|  | ||||
|             thread::spawn(move || { | ||||
|                 thread::sleep(Duration::from_millis(ms as u64)); | ||||
|                 tx.send(()).unwrap(); | ||||
|             }); | ||||
|  | ||||
|             Idle { | ||||
|                 handle: Some(handle), | ||||
|                 timeout: rx, | ||||
|             }.map_err(|_| unreachable!()) | ||||
|         })) | ||||
|     } | ||||
|  | ||||
|     fn buffer_bytes(self, num: usize) -> Box<Future<Item = Handle, Error = Self::Error>> | ||||
|     where Self: Sized + 'static, | ||||
|           Self: Future<Item = Handle>, | ||||
|           Self::Error: fmt::Debug, | ||||
|     { | ||||
|         use futures::future::poll_fn; | ||||
|  | ||||
|         Box::new(self.and_then(move |mut handle| { | ||||
|             // Set tx_rem to num | ||||
|             { | ||||
|                 let mut i = handle.codec.get_mut().inner.lock().unwrap(); | ||||
|                 i.tx_rem = num; | ||||
|             } | ||||
|  | ||||
|             let mut handle = Some(handle); | ||||
|  | ||||
|             poll_fn(move || { | ||||
|                 { | ||||
|                     let mut inner = handle.as_mut().unwrap() | ||||
|                         .codec.get_mut().inner.lock().unwrap(); | ||||
|  | ||||
|                     if inner.tx_rem == 0 { | ||||
|                         inner.tx_rem = usize::MAX; | ||||
|                     } else { | ||||
|                         inner.tx_task = Some(task::current()); | ||||
|                         return Ok(Async::NotReady); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 Ok(handle.take().unwrap().into()) | ||||
|             }) | ||||
|         })) | ||||
|     } | ||||
|  | ||||
|     fn unbounded_bytes(self) -> Box<Future<Item = Handle, Error = Self::Error>> | ||||
|     where Self: Sized + 'static, | ||||
|           Self: Future<Item = Handle>, | ||||
|           Self::Error: fmt::Debug, | ||||
|     { | ||||
|         Box::new(self.and_then(|mut handle| { | ||||
|             { | ||||
|                 let mut i = handle.codec.get_mut().inner.lock().unwrap(); | ||||
|                 i.tx_rem = usize::MAX; | ||||
|  | ||||
|                 if let Some(task) = i.tx_rem_task.take() { | ||||
|                     task.notify(); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             Ok(handle.into()) | ||||
|         })) | ||||
|     } | ||||
|  | ||||
|     fn then_notify(self, tx: oneshot::Sender<()>) -> Box<Future<Item = Handle, Error = Self::Error>> | ||||
|     where Self: Sized + 'static, | ||||
|           Self: Future<Item = Handle>, | ||||
|           Self::Error: fmt::Debug, | ||||
|     { | ||||
|         Box::new(self.map(move |handle| { | ||||
|             tx.send(()).unwrap(); | ||||
|             handle | ||||
|         })) | ||||
|     } | ||||
|  | ||||
|     fn wait_for<F>(self, other: F) -> Box<Future<Item = Self::Item, Error = Self::Error>> | ||||
|     where | ||||
|         F: Future + 'static, | ||||
|         Self: Future + Sized + 'static | ||||
|     { | ||||
|         Box::new(self.then(move |result| { | ||||
|             other.then(move |_| result) | ||||
|         })) | ||||
|     } | ||||
|  | ||||
|     fn close(self) -> Box<Future<Item = (), Error = ()>> | ||||
|     where | ||||
|         Self: Future<Error = ()> + Sized + 'static, | ||||
|     { | ||||
|         Box::new(self.map(drop)) | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub struct RecvFrame<T> { | ||||
|     inner: T, | ||||
|     frame: Frame, | ||||
| } | ||||
|  | ||||
| impl<T> Future for RecvFrame<T> | ||||
| where | ||||
|     T: Future<Item = (Option<Frame>, Handle)>, | ||||
|     T::Error: fmt::Debug, | ||||
| { | ||||
|     type Item = Handle; | ||||
|     type Error = (); | ||||
|  | ||||
|     fn poll(&mut self) -> Poll<Self::Item, Self::Error> { | ||||
|         use self::Frame::Data; | ||||
|  | ||||
|         let (frame, handle) = match self.inner.poll().unwrap() { | ||||
|             Async::Ready((frame, handle)) => (frame, handle), | ||||
|             Async::NotReady => return Ok(Async::NotReady), | ||||
|         }; | ||||
|  | ||||
|         let frame = frame.unwrap(); | ||||
|  | ||||
|         match (frame, &self.frame) { | ||||
|             (Data(ref a), &Data(ref b)) => { | ||||
|                 assert_eq!(a.payload().len(), b.payload().len(), "recv_frame data payload len"); | ||||
|                 assert_eq!(a, b, "recv_frame"); | ||||
|             } | ||||
|             (ref a, b) => { | ||||
|                 assert_eq!(a, b, "recv_frame"); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         Ok(Async::Ready(handle)) | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub struct SendFrameFut<T> { | ||||
|     inner: T, | ||||
|     frame: Option<SendFrame>, | ||||
| } | ||||
|  | ||||
| impl<T> Future for SendFrameFut<T> | ||||
| where | ||||
|     T: Future<Item = Handle>, | ||||
|     T::Error: fmt::Debug, | ||||
| { | ||||
|     type Item = Handle; | ||||
|     type Error = (); | ||||
|  | ||||
|     fn poll(&mut self) -> Poll<Self::Item, Self::Error> { | ||||
|         let mut handle = match self.inner.poll().unwrap() { | ||||
|             Async::Ready(handle) => handle, | ||||
|             Async::NotReady => return Ok(Async::NotReady), | ||||
|         }; | ||||
|         handle.send(self.frame.take().unwrap()).unwrap(); | ||||
|         Ok(Async::Ready(handle)) | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub struct Idle { | ||||
|     handle: Option<Handle>, | ||||
|     timeout: oneshot::Receiver<()>, | ||||
| } | ||||
|  | ||||
| impl Future for Idle { | ||||
|     type Item = Handle; | ||||
|     type Error = (); | ||||
|  | ||||
|     fn poll(&mut self) -> Poll<Self::Item, Self::Error> { | ||||
|         if self.timeout.poll().unwrap().is_ready() { | ||||
|             return Ok(self.handle.take().unwrap().into()); | ||||
|         } | ||||
|  | ||||
|         match self.handle.as_mut().unwrap().poll() { | ||||
|             Ok(Async::NotReady) => Ok(Async::NotReady), | ||||
|             res => { | ||||
|                 panic!("Idle received unexpected frame on handle; frame={:?}", res); | ||||
|             }, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<T> HandleFutureExt for T | ||||
| where | ||||
|     T: Future + 'static, | ||||
| { | ||||
| } | ||||
|  | ||||
| pub trait IntoRecvFrame { | ||||
|     type Future: Future; | ||||
|     fn into_recv_frame(self, frame: Frame) -> RecvFrame<Self::Future>; | ||||
| } | ||||
|  | ||||
| impl IntoRecvFrame for Handle { | ||||
|     type Future = ::futures::stream::StreamFuture<Self>; | ||||
|  | ||||
|     fn into_recv_frame(self, frame: Frame) -> RecvFrame<Self::Future> { | ||||
|         RecvFrame { | ||||
|             inner: self.into_future(), | ||||
|             frame: frame, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<T> IntoRecvFrame for T | ||||
| where | ||||
|     T: Future<Item = Handle> + 'static, | ||||
|     T::Error: fmt::Debug, | ||||
| { | ||||
|     type Future = Box<Future<Item = (Option<Frame>, Handle), Error = ()>>; | ||||
|  | ||||
|     fn into_recv_frame(self, frame: Frame) -> RecvFrame<Self::Future> { | ||||
|         let into_fut = Box::new( | ||||
|             self.unwrap() | ||||
|                 .and_then(|handle| handle.into_future().unwrap()), | ||||
|         ); | ||||
|         RecvFrame { | ||||
|             inner: into_fut, | ||||
|             frame: frame, | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										586
									
								
								tests/h2-support/src/mock_io.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										586
									
								
								tests/h2-support/src/mock_io.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,586 @@ | ||||
| //! A mock type implementing [`Read`] and [`Write`]. | ||||
| //! | ||||
| //! Copied from https://github.com/carllerche/mock-io. | ||||
| //! | ||||
| //! TODO: | ||||
| //! - Either the mock-io crate should be released or this module should be | ||||
| //!   removed from h2. | ||||
| //! | ||||
| //! # Overview | ||||
| //! | ||||
| //! Provides a type that implements [`Read`] + [`Write`] that can be configured | ||||
| //! to handle an arbitrary sequence of read and write operations. This is useful | ||||
| //! for writing unit tests for networking services as using an actual network | ||||
| //! type is fairly non deterministic. | ||||
| //! | ||||
| //! # Usage | ||||
| //! | ||||
| //! Add the following to your `Cargo.toml` | ||||
| //! | ||||
| //! ```toml | ||||
| //! [dependencies] | ||||
| //! mock-io = { git = "https://github.com/carllerche/mock-io" } | ||||
| //! ``` | ||||
| //! | ||||
| //! Then use it in your project. For example, a test could be written: | ||||
| //! | ||||
| //! ``` | ||||
| //! extern crate mock_io; | ||||
| //! | ||||
| //! use mock_io::{Builder, Mock}; | ||||
| //! use std::io::{Read, Write}; | ||||
| //! | ||||
| //! # /* | ||||
| //! #[test] | ||||
| //! # */ | ||||
| //! fn test_io() { | ||||
| //!     let mut mock = Builder::new() | ||||
| //!         .write(b"ping") | ||||
| //!         .read(b"pong") | ||||
| //!         .build(); | ||||
| //! | ||||
| //!    let n = mock.write(b"ping").unwrap(); | ||||
| //!    assert_eq!(n, 4); | ||||
| //! | ||||
| //!    let mut buf = vec![]; | ||||
| //!    mock.read_to_end(&mut buf).unwrap(); | ||||
| //! | ||||
| //!    assert_eq!(buf, b"pong"); | ||||
| //! } | ||||
| //! # pub fn main() { | ||||
| //! # test_io(); | ||||
| //! # } | ||||
| //! ``` | ||||
| //! | ||||
| //! Attempting to write data that the mock isn't expected will result in a | ||||
| //! panic. | ||||
| //! | ||||
| //! # Tokio | ||||
| //! | ||||
| //! `Mock` also supports tokio by implementing `AsyncRead` and `AsyncWrite`. | ||||
| //! When using `Mock` in context of a Tokio task, it will automatically switch | ||||
| //! to "async" behavior (this can also be set explicitly by calling `set_async` | ||||
| //! on `Builder`). | ||||
| //! | ||||
| //! In async mode, calls to read and write are non-blocking and the task using | ||||
| //! the mock is notified when the readiness state changes. | ||||
| //! | ||||
| //! # `io-dump` dump files | ||||
| //! | ||||
| //! `Mock` can also be configured from an `io-dump` file. By doing this, the | ||||
| //! mock value will replay a previously recorded behavior. This is useful for | ||||
| //! collecting a scenario from the real world and replying it as part of a test. | ||||
| //! | ||||
| //! [`Read`]: https://doc.rust-lang.org/std/io/trait.Read.html | ||||
| //! [`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html | ||||
|  | ||||
| #![allow(deprecated)] | ||||
|  | ||||
| use std::{cmp, io}; | ||||
| use std::collections::VecDeque; | ||||
| use std::time::{Duration, Instant}; | ||||
|  | ||||
| /// An I/O handle that follows a predefined script. | ||||
| /// | ||||
| /// This value is created by `Builder` and implements `Read + `Write`. It | ||||
| /// follows the scenario described by the builder and panics otherwise. | ||||
| #[derive(Debug)] | ||||
| pub struct Mock { | ||||
|     inner: Inner, | ||||
|     tokio: tokio::Inner, | ||||
|     async: Option<bool>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| pub struct Handle { | ||||
|     inner: tokio::Handle, | ||||
| } | ||||
|  | ||||
| /// Builds `Mock` instances. | ||||
| #[derive(Debug, Clone, Default)] | ||||
| pub struct Builder { | ||||
|     // Sequence of actions for the Mock to take | ||||
|     actions: VecDeque<Action>, | ||||
|  | ||||
|     // true for Tokio, false for blocking, None to auto detect | ||||
|     async: Option<bool>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| enum Action { | ||||
|     Read(Vec<u8>), | ||||
|     Write(Vec<u8>), | ||||
|     Wait(Duration), | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| struct Inner { | ||||
|     actions: VecDeque<Action>, | ||||
|     waiting: Option<Instant>, | ||||
| } | ||||
|  | ||||
| impl Builder { | ||||
|     /// Return a new, empty `Builder. | ||||
|     pub fn new() -> Self { | ||||
|         Self::default() | ||||
|     } | ||||
|  | ||||
|     /// Sequence a `read` operation. | ||||
|     /// | ||||
|     /// The next operation in the mock's script will be to expect a `read` call | ||||
|     /// and return `buf`. | ||||
|     pub fn read(&mut self, buf: &[u8]) -> &mut Self { | ||||
|         self.actions.push_back(Action::Read(buf.into())); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Sequence a `write` operation. | ||||
|     /// | ||||
|     /// The next operation in the mock's script will be to expect a `write` | ||||
|     /// call. | ||||
|     pub fn write(&mut self, buf: &[u8]) -> &mut Self { | ||||
|         self.actions.push_back(Action::Write(buf.into())); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Sequence a wait. | ||||
|     /// | ||||
|     /// The next operation in the mock's script will be to wait without doing so | ||||
|     /// for `duration` amount of time. | ||||
|     pub fn wait(&mut self, duration: Duration) -> &mut Self { | ||||
|         let duration = cmp::max(duration, Duration::from_millis(1)); | ||||
|         self.actions.push_back(Action::Wait(duration)); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Build a `Mock` value according to the defined script. | ||||
|     pub fn build(&mut self) -> Mock { | ||||
|         let (mock, _) = self.build_with_handle(); | ||||
|         mock | ||||
|     } | ||||
|  | ||||
|     /// Build a `Mock` value paired with a handle | ||||
|     pub fn build_with_handle(&mut self) -> (Mock, Handle) { | ||||
|         let (tokio, handle) = tokio::Inner::new(); | ||||
|  | ||||
|         let src = self.clone(); | ||||
|  | ||||
|         let mock = Mock { | ||||
|             inner: Inner { | ||||
|                 actions: src.actions, | ||||
|                 waiting: None, | ||||
|             }, | ||||
|             tokio: tokio, | ||||
|             async: src.async, | ||||
|         }; | ||||
|  | ||||
|         let handle = Handle { inner: handle }; | ||||
|  | ||||
|         (mock, handle) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Handle { | ||||
|     /// Sequence a `read` operation. | ||||
|     /// | ||||
|     /// The next operation in the mock's script will be to expect a `read` call | ||||
|     /// and return `buf`. | ||||
|     pub fn read(&mut self, buf: &[u8]) -> &mut Self { | ||||
|         self.inner.read(buf); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Sequence a `write` operation. | ||||
|     /// | ||||
|     /// The next operation in the mock's script will be to expect a `write` | ||||
|     /// call. | ||||
|     pub fn write(&mut self, buf: &[u8]) -> &mut Self { | ||||
|         self.inner.write(buf); | ||||
|         self | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Mock { | ||||
|     fn sync_read(&mut self, dst: &mut [u8]) -> io::Result<usize> { | ||||
|         use std::thread; | ||||
|  | ||||
|         loop { | ||||
|             match self.inner.read(dst) { | ||||
|                 Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { | ||||
|                     if let Some(rem) = self.inner.remaining_wait() { | ||||
|                         thread::sleep(rem); | ||||
|                     } else { | ||||
|                         // We've entered a dead lock scenario. The peer expects | ||||
|                         // a write but we are reading. | ||||
|                         panic!("mock_io::Mock expects write but currently blocked in read"); | ||||
|                     } | ||||
|                 } | ||||
|                 ret => return ret, | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn sync_write(&mut self, src: &[u8]) -> io::Result<usize> { | ||||
|         match self.inner.write(src) { | ||||
|             Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { | ||||
|                 panic!("mock_io::Mock not currently expecting a write"); | ||||
|             } | ||||
|             ret => ret, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Returns `true` if running in a futures-rs task context | ||||
|     fn is_async(&self) -> bool { | ||||
|         self.async.unwrap_or(tokio::is_task_ctx()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Inner { | ||||
|     fn read(&mut self, dst: &mut [u8]) -> io::Result<usize> { | ||||
|         match self.action() { | ||||
|             Some(&mut Action::Read(ref mut data)) =>{ | ||||
|                 // Figure out how much to copy | ||||
|                 let n = cmp::min(dst.len(), data.len()); | ||||
|  | ||||
|                 // Copy the data into the `dst` slice | ||||
|                 (&mut dst[..n]).copy_from_slice(&data[..n]); | ||||
|  | ||||
|                 // Drain the data from the source | ||||
|                 data.drain(..n); | ||||
|  | ||||
|                 // Return the number of bytes read | ||||
|                 Ok(n) | ||||
|             } | ||||
|             Some(_) => { | ||||
|                 // Either waiting or expecting a write | ||||
|                 Err(io::ErrorKind::WouldBlock.into()) | ||||
|             } | ||||
|             None => { | ||||
|                  Ok(0) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn write(&mut self, mut src: &[u8]) -> io::Result<usize> { | ||||
|         let mut ret = 0; | ||||
|  | ||||
|         if self.actions.is_empty() { | ||||
|             return Err(io::ErrorKind::BrokenPipe.into()); | ||||
|         } | ||||
|  | ||||
|         match self.action() { | ||||
|             Some(&mut Action::Wait(..)) => { | ||||
|                 return Err(io::ErrorKind::WouldBlock.into()); | ||||
|             } | ||||
|             _ => {} | ||||
|         } | ||||
|  | ||||
|         for i in 0..self.actions.len() { | ||||
|             match self.actions[i] { | ||||
|                 Action::Write(ref mut expect) => { | ||||
|                     let n = cmp::min(src.len(), expect.len()); | ||||
|  | ||||
|                     assert_eq!(&src[..n], &expect[..n]); | ||||
|  | ||||
|                     // Drop data that was matched | ||||
|                     expect.drain(..n); | ||||
|                     src = &src[n..]; | ||||
|  | ||||
|                     ret += n; | ||||
|  | ||||
|                     if src.is_empty() { | ||||
|                         return Ok(ret); | ||||
|                     } | ||||
|                 } | ||||
|                 Action::Wait(..) => { | ||||
|                     break; | ||||
|                 } | ||||
|                 _ => {} | ||||
|             } | ||||
|  | ||||
|             // TODO: remove write | ||||
|         } | ||||
|  | ||||
|         Ok(ret) | ||||
|     } | ||||
|  | ||||
|     fn remaining_wait(&mut self) -> Option<Duration> { | ||||
|         match self.action() { | ||||
|             Some(&mut Action::Wait(dur)) => Some(dur), | ||||
|             _ => None, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn action(&mut self) -> Option<&mut Action> { | ||||
|         loop { | ||||
|             if self.actions.is_empty() { | ||||
|                 return None; | ||||
|             } | ||||
|  | ||||
|             match self.actions[0] { | ||||
|                 Action::Read(ref mut data) => { | ||||
|                     if !data.is_empty() { | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|                 Action::Write(ref mut data) => { | ||||
|                     if !data.is_empty() { | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|                 Action::Wait(ref mut dur) => { | ||||
|                     if let Some(until) = self.waiting { | ||||
|                         let now = Instant::now(); | ||||
|  | ||||
|                         if now < until { | ||||
|                             break; | ||||
|                         } | ||||
|                     } else { | ||||
|                         self.waiting = Some(Instant::now() + *dur); | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             let _action = self.actions.pop_front(); | ||||
|         } | ||||
|  | ||||
|         self.actions.front_mut() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl io::Read for Mock { | ||||
|     fn read(&mut self, dst: &mut [u8]) -> io::Result<usize> { | ||||
|         if self.is_async() { | ||||
|             tokio::async_read(self, dst) | ||||
|         } else { | ||||
|             self.sync_read(dst) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl io::Write for Mock { | ||||
|     fn write(&mut self, src: &[u8]) -> io::Result<usize> { | ||||
|         if self.is_async() { | ||||
|             tokio::async_write(self, src) | ||||
|         } else { | ||||
|             self.sync_write(src) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn flush(&mut self) -> io::Result<()> { | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| // use tokio::*; | ||||
|  | ||||
| mod tokio { | ||||
|     extern crate futures; | ||||
|     extern crate tokio_io; | ||||
|     extern crate tokio_timer; | ||||
|  | ||||
|     use super::*; | ||||
|  | ||||
|     use self::futures::{Future, Stream, Poll, Async}; | ||||
|     use self::futures::sync::mpsc; | ||||
|     use self::futures::task::{self, Task}; | ||||
|     use self::tokio_io::{AsyncRead, AsyncWrite}; | ||||
|     use self::tokio_timer::{Timer, Sleep}; | ||||
|  | ||||
|     use std::io; | ||||
|  | ||||
|     impl Builder { | ||||
|         pub fn set_async(&mut self, is_async: bool) -> &mut Self { | ||||
|             self.async = Some(is_async); | ||||
|             self | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #[derive(Debug)] | ||||
|     pub struct Inner { | ||||
|         timer: Timer, | ||||
|         sleep: Option<Sleep>, | ||||
|         read_wait: Option<Task>, | ||||
|         rx: mpsc::UnboundedReceiver<Action>, | ||||
|     } | ||||
|  | ||||
|     #[derive(Debug)] | ||||
|     pub struct Handle { | ||||
|         tx: mpsc::UnboundedSender<Action>, | ||||
|     } | ||||
|  | ||||
|     // ===== impl Handle ===== | ||||
|  | ||||
|     impl Handle { | ||||
|         pub fn read(&mut self, buf: &[u8]) { | ||||
|             mpsc::UnboundedSender::send(&mut self.tx, Action::Read(buf.into())).unwrap(); | ||||
|         } | ||||
|  | ||||
|         pub fn write(&mut self, buf: &[u8]) { | ||||
|             mpsc::UnboundedSender::send(&mut self.tx, Action::Write(buf.into())).unwrap(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // ===== impl Inner ===== | ||||
|  | ||||
|     impl Inner { | ||||
|         pub fn new() -> (Inner, Handle) { | ||||
|             // TODO: We probably want a higher resolution timer. | ||||
|             let timer = tokio_timer::wheel() | ||||
|                 .tick_duration(Duration::from_millis(1)) | ||||
|                 .max_timeout(Duration::from_secs(3600)) | ||||
|                 .build(); | ||||
|  | ||||
|             let (tx, rx) = mpsc::unbounded(); | ||||
|  | ||||
|             let inner = Inner { | ||||
|                 timer: timer, | ||||
|                 sleep: None, | ||||
|                 read_wait: None, | ||||
|                 rx: rx, | ||||
|             }; | ||||
|  | ||||
|             let handle = Handle { tx }; | ||||
|  | ||||
|             (inner, handle) | ||||
|         } | ||||
|  | ||||
|         pub(super) fn poll_action(&mut self) -> Poll<Option<Action>, ()> { | ||||
|             self.rx.poll() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl Mock { | ||||
|         fn maybe_wakeup_reader(&mut self) { | ||||
|             match self.inner.action() { | ||||
|                 Some(&mut Action::Read(_)) | None => { | ||||
|                     if let Some(task) = self.tokio.read_wait.take() { | ||||
|                         task.notify(); | ||||
|                     } | ||||
|                 } | ||||
|                 _ => {} | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn async_read(me: &mut Mock, dst: &mut [u8]) -> io::Result<usize> { | ||||
|         loop { | ||||
|             if let Some(ref mut sleep) = me.tokio.sleep { | ||||
|                 let res = try!(sleep.poll()); | ||||
|  | ||||
|                 if !res.is_ready() { | ||||
|                     return Err(io::ErrorKind::WouldBlock.into()); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // If a sleep is set, it has already fired | ||||
|             me.tokio.sleep = None; | ||||
|  | ||||
|             match me.inner.read(dst) { | ||||
|                 Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { | ||||
|                     if let Some(rem) = me.inner.remaining_wait() { | ||||
|                         me.tokio.sleep = Some(me.tokio.timer.sleep(rem)); | ||||
|                     } else { | ||||
|                         me.tokio.read_wait = Some(task::current()); | ||||
|                         return Err(io::ErrorKind::WouldBlock.into()); | ||||
|                     } | ||||
|                 } | ||||
|                 Ok(0) => { | ||||
|                     // TODO: Extract | ||||
|                     match me.tokio.poll_action().unwrap() { | ||||
|                         Async::Ready(Some(action)) => { | ||||
|                             me.inner.actions.push_back(action); | ||||
|                             continue; | ||||
|                         } | ||||
|                         Async::Ready(None) => { | ||||
|                             return Ok(0); | ||||
|                         } | ||||
|                         Async::NotReady => { | ||||
|                             return Err(io::ErrorKind::WouldBlock.into()); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 ret => return ret, | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn async_write(me: &mut Mock, src: &[u8]) -> io::Result<usize> { | ||||
|         loop { | ||||
|             if let Some(ref mut sleep) = me.tokio.sleep { | ||||
|                 let res = try!(sleep.poll()); | ||||
|  | ||||
|                 if !res.is_ready() { | ||||
|                     return Err(io::ErrorKind::WouldBlock.into()); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // If a sleep is set, it has already fired | ||||
|             me.tokio.sleep = None; | ||||
|  | ||||
|             match me.inner.write(src) { | ||||
|                 Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { | ||||
|                     if let Some(rem) = me.inner.remaining_wait() { | ||||
|                         me.tokio.sleep = Some(me.tokio.timer.sleep(rem)); | ||||
|                     } else { | ||||
|                         panic!("unexpected WouldBlock"); | ||||
|                     } | ||||
|                 } | ||||
|                 Ok(0) => { | ||||
|                     // TODO: Is this correct? | ||||
|                     if !me.inner.actions.is_empty() { | ||||
|                         return Err(io::ErrorKind::WouldBlock.into()); | ||||
|                     } | ||||
|  | ||||
|                     // TODO: Extract | ||||
|                     match me.tokio.poll_action().unwrap() { | ||||
|                         Async::Ready(Some(action)) => { | ||||
|                             me.inner.actions.push_back(action); | ||||
|                             continue; | ||||
|                         } | ||||
|                         Async::Ready(None) => { | ||||
|                             panic!("unexpected write"); | ||||
|                         } | ||||
|                         Async::NotReady => { | ||||
|                             return Err(io::ErrorKind::WouldBlock.into()); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 ret => { | ||||
|                     me.maybe_wakeup_reader(); | ||||
|                     return ret; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl AsyncRead for Mock { | ||||
|     } | ||||
|  | ||||
|     impl AsyncWrite for Mock { | ||||
|         fn shutdown(&mut self) -> Poll<(), io::Error> { | ||||
|             Ok(Async::Ready(())) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Returns `true` if called from the context of a futures-rs Task | ||||
|     pub fn is_task_ctx() -> bool { | ||||
|         use std::panic; | ||||
|  | ||||
|         // Save the existing panic hook | ||||
|         let h = panic::take_hook(); | ||||
|  | ||||
|         // Install a new one that does nothing | ||||
|         panic::set_hook(Box::new(|_| {})); | ||||
|  | ||||
|         // Attempt to call the fn | ||||
|         let r = panic::catch_unwind(|| task::current()).is_ok(); | ||||
|  | ||||
|         // Re-install the old one | ||||
|         panic::set_hook(h); | ||||
|  | ||||
|         // Return the result | ||||
|         r | ||||
|     } | ||||
| } | ||||
							
								
								
									
										69
									
								
								tests/h2-support/src/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								tests/h2-support/src/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| //! Utilities to support tests. | ||||
|  | ||||
| #[cfg(not(feature = "unstable"))] | ||||
| compile_error!( | ||||
|     "Tests depend on the 'unstable' feature on h2. \ | ||||
|     Retry with `cargo test --features unstable`" | ||||
| ); | ||||
|  | ||||
| pub extern crate bytes; | ||||
| pub extern crate env_logger; | ||||
| pub extern crate futures; | ||||
| pub extern crate h2; | ||||
| pub extern crate http; | ||||
| pub extern crate string; | ||||
| pub extern crate tokio_io; | ||||
|  | ||||
| // Kind of annoying, but we can't use macros from crates that aren't specified | ||||
| // at the root. | ||||
| macro_rules! try_ready { | ||||
|     ($e:expr) => ({ | ||||
|         use $crate::support::futures::Async; | ||||
|         match $e { | ||||
|             Ok(Async::Ready(t)) => t, | ||||
|             Ok(Async::NotReady) => return Ok(Async::NotReady), | ||||
|             Err(e) => return Err(From::from(e)), | ||||
|         } | ||||
|     }) | ||||
| } | ||||
|  | ||||
| macro_rules! try_nb { | ||||
|     ($e:expr) => ({ | ||||
|         use ::std::io::ErrorKind::WouldBlock; | ||||
|         use $crate::support::futures::Async; | ||||
|  | ||||
|         match $e { | ||||
|             Ok(t) => t, | ||||
|             Err(ref e) if e.kind() == WouldBlock => { | ||||
|                 return Ok(Async::NotReady) | ||||
|             } | ||||
|             Err(e) => return Err(e.into()), | ||||
|         } | ||||
|     }) | ||||
| } | ||||
|  | ||||
| #[macro_use] | ||||
| mod assert; | ||||
|  | ||||
| #[macro_use] | ||||
| pub mod raw; | ||||
|  | ||||
| pub mod frames; | ||||
| pub mod prelude; | ||||
| pub mod mock; | ||||
| pub mod mock_io; | ||||
| pub mod notify; | ||||
| pub mod util; | ||||
|  | ||||
| mod future_ext; | ||||
|  | ||||
| pub use self::future_ext::{FutureExt, Unwrap}; | ||||
|  | ||||
| pub type WindowSize = usize; | ||||
| pub const DEFAULT_WINDOW_SIZE: WindowSize = (1 << 16) - 1; | ||||
|  | ||||
| // This is our test Codec type | ||||
| pub type Codec<T> = h2::Codec<T, ::std::io::Cursor<::bytes::Bytes>>; | ||||
|  | ||||
| // This is the frame type that is sent | ||||
| pub type SendFrame = h2::frame::Frame<::std::io::Cursor<::bytes::Bytes>>; | ||||
							
								
								
									
										55
									
								
								tests/h2-support/src/notify.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								tests/h2-support/src/notify.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| use futures::executor::{self, Notify}; | ||||
|  | ||||
| use std::sync::Arc; | ||||
| use std::sync::atomic::AtomicBool; | ||||
| use std::sync::atomic::Ordering::SeqCst; | ||||
|  | ||||
| pub struct MockNotify { | ||||
|     inner: Arc<Inner>, | ||||
| } | ||||
|  | ||||
| struct Inner { | ||||
|     notified: AtomicBool, | ||||
| } | ||||
|  | ||||
| impl MockNotify { | ||||
|     pub fn new() -> Self { | ||||
|         MockNotify { | ||||
|             inner: Arc::new(Inner { | ||||
|                 notified: AtomicBool::new(false), | ||||
|             }), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn with<F: FnOnce() -> R, R>(&self, f: F) -> R { | ||||
|         use futures::Async::Ready; | ||||
|         use futures::future::poll_fn; | ||||
|  | ||||
|         self.clear(); | ||||
|  | ||||
|         let mut f = Some(f); | ||||
|  | ||||
|         let res = executor::spawn(poll_fn(move || { | ||||
|             Ok::<_, ()>(Ready(f.take().unwrap()())) | ||||
|         })).poll_future_notify(&self.inner, 0); | ||||
|  | ||||
|         match res { | ||||
|             Ok(Ready(v)) => v, | ||||
|             _ => unreachable!(), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn clear(&self) { | ||||
|         self.inner.notified.store(false, SeqCst); | ||||
|     } | ||||
|  | ||||
|     pub fn is_notified(&self) -> bool { | ||||
|         self.inner.notified.load(SeqCst) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Notify for Inner { | ||||
|     fn notify(&self, _: usize) { | ||||
|         self.notified.store(true, SeqCst); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										112
									
								
								tests/h2-support/src/prelude.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								tests/h2-support/src/prelude.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,112 @@ | ||||
|  | ||||
| // Re-export H2 crate | ||||
| pub use super::h2; | ||||
|  | ||||
| pub use self::h2::*; | ||||
| pub use self::h2::client; | ||||
| pub use self::h2::frame::StreamId; | ||||
| pub use self::h2::server; | ||||
|  | ||||
| // Re-export mock | ||||
| pub use super::mock::{self, HandleFutureExt}; | ||||
|  | ||||
| // Re-export frames helpers | ||||
| pub use super::frames; | ||||
|  | ||||
| // Re-export mock notify | ||||
| pub use super::notify::MockNotify; | ||||
|  | ||||
| // Re-export utility mod | ||||
| pub use super::util; | ||||
|  | ||||
| // Re-export some type defines | ||||
| pub use super::{Codec, SendFrame}; | ||||
|  | ||||
| // Re-export useful crates | ||||
| pub use super::{bytes, env_logger, futures, http, mock_io, tokio_io}; | ||||
|  | ||||
| // Re-export primary future types | ||||
| pub use self::futures::{Future, IntoFuture, Sink, Stream}; | ||||
|  | ||||
| // And our Future extensions | ||||
| pub use super::future_ext::{FutureExt, Unwrap}; | ||||
|  | ||||
| // Re-export HTTP types | ||||
| pub use self::http::{uri, HeaderMap, Method, Request, Response, StatusCode, Version}; | ||||
|  | ||||
| pub use self::bytes::{Buf, BufMut, Bytes, BytesMut, IntoBuf}; | ||||
|  | ||||
| pub use tokio_io::{AsyncRead, AsyncWrite}; | ||||
|  | ||||
| pub use std::time::Duration; | ||||
|  | ||||
| // ===== Everything under here shouldn't be used ===== | ||||
| // TODO: work on deleting this code | ||||
|  | ||||
| pub use futures::future::poll_fn; | ||||
|  | ||||
| pub trait MockH2 { | ||||
|     fn handshake(&mut self) -> &mut Self; | ||||
| } | ||||
|  | ||||
| impl MockH2 for mock_io::Builder { | ||||
|     fn handshake(&mut self) -> &mut Self { | ||||
|         self.write(b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n") | ||||
|             // Settings frame | ||||
|             .write(frames::SETTINGS) | ||||
|             .read(frames::SETTINGS) | ||||
|             .read(frames::SETTINGS_ACK) | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub trait ClientExt { | ||||
|     fn run<F: Future>(&mut self, f: F) -> Result<F::Item, F::Error>; | ||||
| } | ||||
|  | ||||
| impl<T, B> ClientExt for client::Connection<T, B> | ||||
| where | ||||
|     T: AsyncRead + AsyncWrite + 'static, | ||||
|     B: IntoBuf + 'static, | ||||
| { | ||||
|     fn run<F: Future>(&mut self, f: F) -> Result<F::Item, F::Error> { | ||||
|         use futures::future::{self, Future}; | ||||
|         use futures::future::Either::*; | ||||
|  | ||||
|         let res = future::poll_fn(|| self.poll()).select2(f).wait(); | ||||
|  | ||||
|         match res { | ||||
|             Ok(A((_, b))) => { | ||||
|                 // Connection is done... | ||||
|                 b.wait() | ||||
|             }, | ||||
|             Ok(B((v, _))) => return Ok(v), | ||||
|             Err(A((e, _))) => panic!("err: {:?}", e), | ||||
|             Err(B((e, _))) => return Err(e), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub fn build_large_headers() -> Vec<(&'static str, String)> { | ||||
|     vec![ | ||||
|         ("one", "hello".to_string()), | ||||
|         ("two", build_large_string('2', 4 * 1024)), | ||||
|         ("three", "three".to_string()), | ||||
|         ("four", build_large_string('4', 4 * 1024)), | ||||
|         ("five", "five".to_string()), | ||||
|         ("six", build_large_string('6', 4 * 1024)), | ||||
|         ("seven", "seven".to_string()), | ||||
|         ("eight", build_large_string('8', 4 * 1024)), | ||||
|         ("nine", "nine".to_string()), | ||||
|         ("ten", build_large_string('0', 4 * 1024)), | ||||
|     ] | ||||
| } | ||||
|  | ||||
| fn build_large_string(ch: char, len: usize) -> String { | ||||
|     let mut ret = String::new(); | ||||
|  | ||||
|     for _ in 0..len { | ||||
|         ret.push(ch); | ||||
|     } | ||||
|  | ||||
|     ret | ||||
| } | ||||
							
								
								
									
										52
									
								
								tests/h2-support/src/raw.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								tests/h2-support/src/raw.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | ||||
| // ===== Build a codec from raw bytes ===== | ||||
|  | ||||
| #[macro_export] | ||||
| macro_rules! raw_codec { | ||||
|     ( | ||||
|         $( | ||||
|             $fn:ident => [$($chunk:expr,)+]; | ||||
|         )* | ||||
|     ) => {{ | ||||
|         let mut b = $crate::mock_io::Builder::new(); | ||||
|  | ||||
|         $({ | ||||
|             let mut chunk = vec![]; | ||||
|  | ||||
|             $( | ||||
|                 $crate::raw::Chunk::push(&$chunk, &mut chunk); | ||||
|             )+ | ||||
|  | ||||
|             b.$fn(&chunk[..]); | ||||
|         })* | ||||
|  | ||||
|         $crate::Codec::new(b.build()) | ||||
|     }} | ||||
| } | ||||
|  | ||||
| pub trait Chunk { | ||||
|     fn push(&self, dst: &mut Vec<u8>); | ||||
| } | ||||
|  | ||||
| impl Chunk for u8 { | ||||
|     fn push(&self, dst: &mut Vec<u8>) { | ||||
|         dst.push(*self); | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a> Chunk for &'a [u8] { | ||||
|     fn push(&self, dst: &mut Vec<u8>) { | ||||
|         dst.extend(*self) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a> Chunk for &'a str { | ||||
|     fn push(&self, dst: &mut Vec<u8>) { | ||||
|         dst.extend(self.as_bytes()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Chunk for Vec<u8> { | ||||
|     fn push(&self, dst: &mut Vec<u8>) { | ||||
|         dst.extend(self.iter()) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										44
									
								
								tests/h2-support/src/util.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								tests/h2-support/src/util.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| use h2; | ||||
|  | ||||
| use super::string::{String, TryFrom}; | ||||
| use bytes::Bytes; | ||||
| use futures::{Async, Future, Poll}; | ||||
|  | ||||
| pub fn byte_str(s: &str) -> String<Bytes> { | ||||
|     String::try_from(Bytes::from(s)).unwrap() | ||||
| } | ||||
|  | ||||
| pub fn wait_for_capacity(stream: h2::SendStream<Bytes>, target: usize) -> WaitForCapacity { | ||||
|     WaitForCapacity { | ||||
|         stream: Some(stream), | ||||
|         target: target, | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub struct WaitForCapacity { | ||||
|     stream: Option<h2::SendStream<Bytes>>, | ||||
|     target: usize, | ||||
| } | ||||
|  | ||||
| impl WaitForCapacity { | ||||
|     fn stream(&mut self) -> &mut h2::SendStream<Bytes> { | ||||
|         self.stream.as_mut().unwrap() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Future for WaitForCapacity { | ||||
|     type Item = h2::SendStream<Bytes>; | ||||
|     type Error = (); | ||||
|  | ||||
|     fn poll(&mut self) -> Poll<Self::Item, ()> { | ||||
|         let _ = try_ready!(self.stream().poll_capacity().map_err(|_| panic!())); | ||||
|  | ||||
|         let act = self.stream().capacity(); | ||||
|  | ||||
|         if act >= self.target { | ||||
|             return Ok(self.stream.take().unwrap().into()); | ||||
|         } | ||||
|  | ||||
|         Ok(Async::NotReady) | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user