diff --git a/.travis.yml b/.travis.yml index 78cc45f..8a7a883 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,8 +13,11 @@ matrix: - travis-cargo doc-upload script: + # Run tests - cargo test + # Test examples in readme - rustdoc --test README.md -L target/debug/deps + # Generate docs - cargo doc --no-deps env: diff --git a/Cargo.toml b/Cargo.toml index db5b002..1e55be5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,16 @@ name = "h2" version = "0.1.0" authors = ["Carl Lerche "] +[features] + +# Enables **unstable** APIs. Any API exposed by this feature has no backwards +# compatibility guarantees. In other words, you should not use this feature for +# anything besides experimentation. Definitely **do not** publish a crate that +# depends on this feature. +unstable = [] + +[workspace] + [dependencies] futures = "0.1" tokio-io = "0.1.3" @@ -16,7 +26,14 @@ slab = "0.4.0" string = { git = "https://github.com/carllerche/string" } [dev-dependencies] -mock-io = { git = "https://github.com/carllerche/mock-io", branch = "experiments" } + +# Support code for tests. Ideally this wouldn't be released to crates.io, but +# until rust-lang/cargo#4466 is resolved, we just have to publish this junk crate. +# +# The dependency is set on a fixed version as the `h2-test-support` offers no +# guarantees of backwards compatibility across minor versions. The version of +# `h2-test-support` should always match the current version of `h2`. +h2-test-support = { version = "= 0.1.0", path = "tests/support" } # Fuzzing quickcheck = "0.4.1" diff --git a/examples/akamai.rs b/examples/akamai.rs index 9e5ee73..e0275fb 100644 --- a/examples/akamai.rs +++ b/examples/akamai.rs @@ -2,7 +2,6 @@ extern crate h2; extern crate http; extern crate futures; extern crate rustls; -extern crate tokio_io; extern crate tokio_core; extern crate tokio_rustls; extern crate webpki_roots; diff --git a/examples/client.rs b/examples/client.rs index ffe31fa..dfa177b 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -2,12 +2,10 @@ extern crate h2; extern crate http; extern crate bytes; extern crate futures; -extern crate tokio_io; extern crate tokio_core; extern crate io_dump; extern crate env_logger; -use h2::*; use h2::client::{Client, Body}; use http::*; diff --git a/examples/server-tr.rs b/examples/server-tr.rs index 5f7dc05..4951674 100644 --- a/examples/server-tr.rs +++ b/examples/server-tr.rs @@ -2,9 +2,7 @@ extern crate h2; extern crate http; extern crate bytes; extern crate futures; -extern crate tokio_io; extern crate tokio_core; -extern crate io_dump; extern crate env_logger; use h2::server::Server; diff --git a/examples/server.rs b/examples/server.rs index 5c4ea90..39b0785 100644 --- a/examples/server.rs +++ b/examples/server.rs @@ -2,9 +2,7 @@ extern crate h2; extern crate http; extern crate bytes; extern crate futures; -extern crate tokio_io; extern crate tokio_core; -extern crate io_dump; extern crate env_logger; use h2::server::Server; diff --git a/src/codec/framed_write.rs b/src/codec/framed_write.rs index df3fd97..19052a4 100644 --- a/src/codec/framed_write.rs +++ b/src/codec/framed_write.rs @@ -258,3 +258,14 @@ impl AsyncRead for FramedWrite { self.inner.prepare_uninitialized_buffer(buf) } } + +#[cfg(feature = "unstable")] +mod unstable { + use super::*; + + impl FramedWrite { + pub fn get_ref(&self) -> &T { + &self.inner + } + } +} diff --git a/src/codec/mod.rs b/src/codec/mod.rs index 7f84e30..9762ab3 100644 --- a/src/codec/mod.rs +++ b/src/codec/mod.rs @@ -65,6 +65,11 @@ impl Codec { self.inner.get_ref().max_frame_size() } + #[cfg(feature = "unstable")] + pub fn get_ref(&self) -> &T { + self.inner.get_ref().get_ref() + } + pub fn get_mut(&mut self) -> &mut T { self.inner.get_mut().get_mut() } @@ -146,3 +151,12 @@ impl Sink for Codec Ok(Async::Ready(())) } } + +// TODO: remove (or improve) this +impl From for Codec> + where T: AsyncRead + AsyncWrite, +{ + fn from(src: T) -> Self { + Self::new(src) + } +} diff --git a/src/frame/data.rs b/src/frame/data.rs index d52d598..fbaca6f 100644 --- a/src/frame/data.rs +++ b/src/frame/data.rs @@ -3,6 +3,7 @@ use bytes::{BufMut, Bytes, Buf}; use std::fmt; +#[derive(Eq, PartialEq)] pub struct Data { stream_id: StreamId, data: T, diff --git a/src/frame/go_away.rs b/src/frame/go_away.rs index 0188111..01665ad 100644 --- a/src/frame/go_away.rs +++ b/src/frame/go_away.rs @@ -2,7 +2,7 @@ use frame::{self, Head, Error, Kind, StreamId, Reason}; use bytes::{BufMut, BigEndian}; -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] pub struct GoAway { last_stream_id: StreamId, error_code: u32, diff --git a/src/frame/headers.rs b/src/frame/headers.rs index 4694710..e44a9aa 100644 --- a/src/frame/headers.rs +++ b/src/frame/headers.rs @@ -15,6 +15,7 @@ use std::io::Cursor; /// Header frame /// /// This could be either a request or a response. +#[derive(Eq, PartialEq)] pub struct Headers { /// The ID of the stream with which this frame is associated. stream_id: StreamId, @@ -36,7 +37,7 @@ pub struct Headers { #[derive(Copy, Clone, Eq, PartialEq)] pub struct HeadersFlag(u8); -#[derive(Debug)] +#[derive(Debug, Eq, PartialEq)] pub struct PushPromise { /// The ID of the stream with which this frame is associated. stream_id: StreamId, @@ -63,7 +64,7 @@ pub struct Continuation { headers: Iter, } -#[derive(Debug, Default)] +#[derive(Debug, Default, Eq, PartialEq)] pub struct Pseudo { // Request pub method: Option, diff --git a/src/frame/mod.rs b/src/frame/mod.rs index 7d75565..5063bbb 100644 --- a/src/frame/mod.rs +++ b/src/frame/mod.rs @@ -61,6 +61,7 @@ pub type FrameSize = u32; pub const HEADER_LEN: usize = 9; +#[derive(Eq, PartialEq)] pub enum Frame { Data(Data), Headers(Headers), diff --git a/src/frame/ping.rs b/src/frame/ping.rs index eb0e52c..16bbec6 100644 --- a/src/frame/ping.rs +++ b/src/frame/ping.rs @@ -5,7 +5,7 @@ const ACK_FLAG: u8 = 0x1; pub type Payload = [u8; 8]; -#[derive(Debug)] +#[derive(Debug, Eq, PartialEq)] pub struct Ping { ack: bool, payload: Payload, diff --git a/src/frame/priority.rs b/src/frame/priority.rs index 253a5ef..cb23795 100644 --- a/src/frame/priority.rs +++ b/src/frame/priority.rs @@ -1,12 +1,12 @@ use frame::*; -#[derive(Debug)] +#[derive(Debug, Eq, PartialEq)] pub struct Priority { stream_id: StreamId, dependency: StreamDependency, } -#[derive(Debug)] +#[derive(Debug, Eq, PartialEq)] pub struct StreamDependency { /// The ID of the stream dependency target dependency_id: StreamId, diff --git a/src/frame/reset.rs b/src/frame/reset.rs index c819569..37a4533 100644 --- a/src/frame/reset.rs +++ b/src/frame/reset.rs @@ -2,7 +2,7 @@ use frame::{self, Head, Error, Kind, StreamId, Reason}; use bytes::{BufMut, BigEndian}; -#[derive(Debug)] +#[derive(Debug, Eq, PartialEq)] pub struct Reset { stream_id: StreamId, error_code: u32, diff --git a/src/frame/util.rs b/src/frame/util.rs index fa4cd2f..decc030 100644 --- a/src/frame/util.rs +++ b/src/frame/util.rs @@ -29,8 +29,8 @@ pub fn strip_padding(payload: &mut Bytes) -> Result { return Err(Error::TooMuchPadding); } - let _ = payload.split_off(pad_len); let _ = payload.split_to(1); + let _ = payload.split_off(pad_len); Ok(pad_len as u8) } diff --git a/src/frame/window_update.rs b/src/frame/window_update.rs index 19dffb8..b6ba7b8 100644 --- a/src/frame/window_update.rs +++ b/src/frame/window_update.rs @@ -4,7 +4,7 @@ use bytes::{BufMut, BigEndian}; const SIZE_INCREMENT_MASK: u32 = 1 << 31; -#[derive(Copy, Clone, Debug)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct WindowUpdate { stream_id: StreamId, size_increment: u32, diff --git a/src/lib.rs b/src/lib.rs index 942a889..d5030fa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -// #![deny(warnings, missing_debug_implementations)] +#![deny(warnings, missing_debug_implementations)] #[macro_use] extern crate futures; @@ -28,9 +28,17 @@ mod error; mod codec; mod hpack; mod proto; + +#[cfg(not(feature = "unstable"))] mod frame; +#[cfg(feature = "unstable")] +pub mod frame; + pub mod client; pub mod server; pub use error::{Error, Reason}; + +#[cfg(feature = "unstable")] +pub use codec::{Codec, SendError, RecvError, UserError}; diff --git a/tests/client_request.rs b/tests/client_request.rs index ddb19db..4015605 100644 --- a/tests/client_request.rs +++ b/tests/client_request.rs @@ -1,9 +1,8 @@ #[macro_use] extern crate log; -#[macro_use] -pub mod support; -use support::*; +extern crate h2_test_support; +use h2_test_support::*; #[test] fn handshake() { diff --git a/tests/codec_read.rs b/tests/codec_read.rs new file mode 100644 index 0000000..7c19578 --- /dev/null +++ b/tests/codec_read.rs @@ -0,0 +1,48 @@ +#[macro_use] +extern crate h2_test_support; +use h2_test_support::*; + +#[test] +fn read_none() { + let mut codec = Codec::from( + mock_io::Builder::new() + .build()); + + assert_closed!(codec); +} + +#[test] +fn read_data_no_padding() { + let mut codec = raw_codec! { + read => [ + 0, 0, 5, 0, 0, 0, 0, 0, 1, + "hello", + ]; + }; + + let data = poll_data!(codec); + assert_eq!(data.stream_id(), 1); + assert_eq!(data.payload(), &b"hello"[..]); + assert!(!data.is_end_stream()); + + assert_closed!(codec); +} + +#[test] +fn read_data_padding() { + let mut codec = raw_codec! { + read => [ + 0, 0, 11, 0, 0x8, 0, 0, 0, 1, + 5, // Pad length + "hello", // Data + "world", // Padding + ]; + }; + + let data = poll_data!(codec); + assert_eq!(data.stream_id(), 1); + assert_eq!(data.payload(), &b"hello"[..]); + assert!(!data.is_end_stream()); + + assert_closed!(codec); +} diff --git a/tests/codec_write.rs b/tests/codec_write.rs new file mode 100644 index 0000000..e69de29 diff --git a/tests/flow_control.rs b/tests/flow_control.rs index 00a2ef6..4d19e0c 100644 --- a/tests/flow_control.rs +++ b/tests/flow_control.rs @@ -1,5 +1,5 @@ -pub mod support; -use support::*; +extern crate h2_test_support; +use h2_test_support::*; // In this case, the stream & connection both have capacity, but capacity is not // explicitly requested. diff --git a/tests/ping_pong.rs b/tests/ping_pong.rs index 2d5d069..53475ce 100644 --- a/tests/ping_pong.rs +++ b/tests/ping_pong.rs @@ -1,5 +1,7 @@ -pub mod support; -use support::*; +/* +extern crate h2_test_support; +use h2_test_support::*; +*/ #[test] #[ignore] diff --git a/tests/prioritization.rs b/tests/prioritization.rs index d6fd1c9..632d472 100644 --- a/tests/prioritization.rs +++ b/tests/prioritization.rs @@ -1,5 +1,5 @@ -pub mod support; -use support::*; +extern crate h2_test_support; +use h2_test_support::*; #[test] fn single_stream_send_large_body() { diff --git a/tests/server.rs b/tests/server.rs index c41dfee..ae55409 100644 --- a/tests/server.rs +++ b/tests/server.rs @@ -1,12 +1,5 @@ -extern crate h2; -extern crate http; -extern crate futures; -extern crate mock_io; -extern crate env_logger; - -use h2::server::Server; - -use futures::*; +extern crate h2_test_support; +use h2_test_support::*; const SETTINGS: &'static [u8] = &[0, 0, 0, 4, 0, 0, 0, 0, 0]; const SETTINGS_ACK: &'static [u8] = &[0, 0, 0, 4, 1, 0, 0, 0, 0]; diff --git a/tests/stream_states.rs b/tests/stream_states.rs index a5b2520..0262a31 100644 --- a/tests/stream_states.rs +++ b/tests/stream_states.rs @@ -1,9 +1,8 @@ #[macro_use] extern crate log; -#[macro_use] -pub mod support; -use support::*; +extern crate h2_test_support; +use h2_test_support::*; #[test] fn send_recv_headers_only() { diff --git a/tests/support/Cargo.toml b/tests/support/Cargo.toml new file mode 100644 index 0000000..0444a74 --- /dev/null +++ b/tests/support/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "h2-test-support" +version = "0.1.0" +authors = ["Carl Lerche "] + +[dependencies] +futures = "0.1" +tokio-io = "0.1" +bytes = "0.4" +http = { git = "https://github.com/carllerche/http", rev = "2dd15d9" } +env_logger = "0.4" + +mock-io = { git = "https://github.com/carllerche/mock-io", branch = "experiments" } + +[dependencies.h2] +path = "../.." +features = ["unstable"] diff --git a/tests/support/README.md b/tests/support/README.md new file mode 100644 index 0000000..811e449 --- /dev/null +++ b/tests/support/README.md @@ -0,0 +1,3 @@ +**This is not a real crate** + +You should not use this crate, it only exists to work around cargo limitations. diff --git a/tests/support/src/codec.rs b/tests/support/src/codec.rs new file mode 100644 index 0000000..90a0dfe --- /dev/null +++ b/tests/support/src/codec.rs @@ -0,0 +1,64 @@ +#[macro_export] +macro_rules! assert_closed { + ($transport:expr) => {{ + assert_eq!($transport.poll().unwrap(), None.into()); + }} +} + +#[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), + } + }} +} + +#[macro_export] +macro_rules! raw_codec { + ( + $( + $fn:ident => [$($chunk:expr,)+]; + )* + ) => {{ + let mut b = $crate::mock_io::Builder::new(); + + $({ + let mut chunk = vec![]; + + $( + $crate::codec::Chunk::push(&$chunk, &mut chunk); + )+ + + b.$fn(&chunk[..]); + })* + + $crate::Codec::new(b.build()) + }} +} + +pub trait Chunk { + fn push(&self, dst: &mut Vec); +} + +impl Chunk for u8 { + fn push(&self, dst: &mut Vec) { + dst.push(*self); + } +} + +impl<'a> Chunk for &'a [u8] { + fn push(&self, dst: &mut Vec) { + dst.extend(*self) + } +} + +impl<'a> Chunk for &'a str { + fn push(&self, dst: &mut Vec) { + dst.extend(self.as_bytes()) + } +} diff --git a/tests/support/mod.rs b/tests/support/src/lib.rs similarity index 92% rename from tests/support/mod.rs rename to tests/support/src/lib.rs index b80e61a..d66d8b1 100644 --- a/tests/support/mod.rs +++ b/tests/support/src/lib.rs @@ -1,7 +1,5 @@ //! Utilities to support tests. -#![allow(unused_extern_crates)] - pub extern crate bytes; pub extern crate h2; pub extern crate http; @@ -10,6 +8,9 @@ pub extern crate futures; pub extern crate mock_io; pub extern crate env_logger; +#[macro_use] +pub mod codec; + pub use self::futures::{ Future, Sink, @@ -27,8 +28,11 @@ pub use self::http::{ HeaderMap, }; +pub use self::h2::*; pub use self::h2::client::{self, Client}; -// pub use self::h2::server; +pub use self::h2::server::{self, Server}; + +pub type Codec = h2::Codec>; pub use self::bytes::{ Buf, diff --git a/tests/trailers.rs b/tests/trailers.rs index 98fd0da..f4982ae 100644 --- a/tests/trailers.rs +++ b/tests/trailers.rs @@ -1,9 +1,8 @@ #[macro_use] extern crate log; -#[macro_use] -pub mod support; -use support::*; +extern crate h2_test_support; +use h2_test_support::*; #[test] fn recv_trailers_only() { diff --git a/util/h2-codec/Cargo.toml b/util/h2-codec/Cargo.toml deleted file mode 100644 index 5f60ddf..0000000 --- a/util/h2-codec/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "h2-codec" -version = "0.1.0" -authors = ["Carl Lerche "] - -[dependencies] -http = { git = "https://github.com/carllerche/http" } -log = "0.3.8" -fnv = "1.0.5" -bytes = "0.4" -string = { git = "https://github.com/carllerche/string" } -byteorder = "1.0" -futures = "0.1" -tokio-io = "0.1.3" - -# tokio-timer = "0.1" -# http = { git = "https://github.com/carllerche/http", branch = "lower-case-header-name-parsing" } -# slab = "0.4.0" diff --git a/util/h2-codec/src/lib.rs b/util/h2-codec/src/lib.rs deleted file mode 100644 index 9f7374a..0000000 --- a/util/h2-codec/src/lib.rs +++ /dev/null @@ -1,22 +0,0 @@ -extern crate http; -extern crate fnv; -extern crate bytes; -extern crate string; -extern crate byteorder; - -extern crate futures; - -#[macro_use] -extern crate tokio_io; - -#[macro_use] -extern crate log; - -#[path = "../../../src/hpack/mod.rs"] -mod hpack; - -#[path = "../../../src/frame/mod.rs"] -mod frame; - -#[path = "../../../src/codec/mod.rs"] -mod codec;