use super::fixture::noop_waker;
use crate::{
Conn, Headers, HttpContext, Method,
h2::{
H2Driver, H2Transport,
acceptor::types::{CloseOutcome, DriverState},
connection::H2Connection,
role::Role,
},
headers::hpack::PseudoHeaders,
};
use std::{
sync::Arc,
task::{Context, Poll},
};
use trillium_testing::TestTransport;
struct TwoDrivers {
client: H2Driver<TestTransport>,
client_conn: Arc<H2Connection>,
client_finished: bool,
server: H2Driver<TestTransport>,
server_finished: bool,
}
impl TwoDrivers {
fn new() -> Self {
let (client_t, server_t) = TestTransport::new();
let client_conn = H2Connection::new(Arc::new(HttpContext::new()));
let server_conn = H2Connection::new(Arc::new(HttpContext::new()));
let client = H2Driver::new(client_conn.clone(), client_t, Role::Client);
let server = H2Driver::new(server_conn.clone(), server_t, Role::Server);
Self {
client,
client_conn,
client_finished: false,
server,
server_finished: false,
}
}
fn pump(&mut self) -> Vec<Conn<H2Transport>> {
let waker = noop_waker();
let mut cx = Context::from_waker(&waker);
let mut conns = Vec::new();
for _ in 0..100 {
if !self.server_finished {
match self.server.drive(&mut cx) {
Poll::Ready(Some(Ok(conn))) => conns.push(conn),
Poll::Ready(Some(Err(_))) => {}
Poll::Ready(None) => self.server_finished = true,
Poll::Pending => {}
}
}
if !self.client_finished {
match self.client.drive(&mut cx) {
Poll::Ready(Some(Ok(_)) | Some(Err(_))) => {}
Poll::Ready(None) => self.client_finished = true,
Poll::Pending => {}
}
}
}
conns
}
}
fn client_get_pseudos() -> PseudoHeaders<'static> {
PseudoHeaders::default()
.with_method(Method::Get)
.with_path("/")
.with_scheme("http")
.with_authority("test")
}
#[test]
fn concurrent_client_streams_open_in_id_order() {
let mut d = TwoDrivers::new();
d.pump();
assert_eq!(d.client.state, DriverState::Running, "client handshake");
assert_eq!(d.server.state, DriverState::Running, "server handshake");
const N: usize = 8;
let handles: Vec<_> = (0..N)
.map(|_| {
d.client_conn
.open_stream(client_get_pseudos(), Headers::new(), None)
.expect("open_stream on a running client connection")
})
.collect();
let ids: Vec<u32> = handles.iter().map(|(id, _, _)| *id).collect();
assert_eq!(
ids,
vec![1, 3, 5, 7, 9, 11, 13, 15],
"client ids are sequential odd"
);
let conns = d.pump();
assert_eq!(
conns.len(),
N,
"server should yield a Conn for every opened stream"
);
assert!(
d.server.close_outcome.is_none() && d.server.state == DriverState::Running,
"server must not protocol-error on the stream opens (state={:?}, outcome={:?})",
d.server.state,
d.server.close_outcome,
);
drop(handles);
}
#[test]
fn double_reset_then_graceful_shutdown_drains() {
let mut d = TwoDrivers::new();
d.pump();
assert_eq!(d.client.state, DriverState::Running, "client handshake");
assert_eq!(d.server.state, DriverState::Running, "server handshake");
let (_id, submit, client_transport) = d
.client_conn
.open_stream(client_get_pseudos(), Headers::new(), None)
.expect("open_stream on a running client connection");
let mut conns = d.pump();
let server_conn = conns
.pop()
.expect("server should yield a Conn for the request");
assert!(conns.is_empty(), "exactly one request stream expected");
drop(submit);
drop(client_transport);
drop(server_conn);
d.server.begin_close(CloseOutcome::Graceful);
d.pump();
assert!(
d.server_finished,
"server's graceful shutdown must drain and finish (state={:?})",
d.server.state,
);
assert!(
d.client_finished,
"client driver must finish once the stream is reset and the connection closes (state={:?})",
d.client.state,
);
}
#[test]
fn server_abandon_and_shutdown_with_client_awaiting_response_drains() {
let mut d = TwoDrivers::new();
d.pump();
let (_id, _submit, _client_transport) = d
.client_conn
.open_stream(client_get_pseudos(), Headers::new(), None)
.expect("open_stream");
let mut conns = d.pump();
let server_conn = conns.pop().expect("server should yield a Conn");
drop(server_conn);
d.server.begin_close(CloseOutcome::Graceful);
d.pump();
assert!(
d.server_finished,
"server must finish (state={:?})",
d.server.state,
);
assert!(
d.client_finished,
"client awaiting a response must finish once the server resets + closes (state={:?})",
d.client.state,
);
}