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:
15
tests/h2-support/Cargo.toml
Normal file
15
tests/h2-support/Cargo.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "h2-support"
|
||||
version = "0.1.0"
|
||||
authors = ["Carl Lerche <me@carllerche.com>"]
|
||||
|
||||
[dependencies]
|
||||
h2 = { path = "../..", features = ["unstable"] }
|
||||
|
||||
bytes = "0.4.7"
|
||||
env_logger = "0.5.9"
|
||||
futures = "0.1.21"
|
||||
http = "0.1.5"
|
||||
string = "0.1.0"
|
||||
tokio-io = "0.1.6"
|
||||
tokio-timer = "0.1.2"
|
||||
5
tests/h2-support/README.md
Normal file
5
tests/h2-support/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# h2 test support
|
||||
|
||||
This crate includes utilities for writing h2 tests. This is broken up into a
|
||||
separate crate because it requires the `unstable` feature flag and to enable
|
||||
`tower-h2` to use the same helpers.
|
||||
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