use super::fixture::*;
use crate::{
Method,
h2::{
H2Error, H2ErrorCode,
frame::{Frame, ping},
},
};
use futures_lite::io::AsyncRead;
use std::{
future::Future,
net::Shutdown,
pin::Pin,
task::{Context, Poll},
};
use trillium_testing::TestTransport;
fn peer_sees_end_of_stream(peer: &mut TestTransport, cx: &mut Context<'_>) -> bool {
let mut buf = [0; 1024];
loop {
match Pin::new(&mut *peer).poll_read(cx, &mut buf) {
Poll::Ready(Ok(0)) => return true,
Poll::Ready(Ok(_)) => {}
Poll::Ready(Err(e)) => panic!("peer read failed: {e}"),
Poll::Pending => return false,
}
}
}
#[test]
fn close_shuts_down_sending_then_waits_for_the_peer() {
let mut fx = DriverFixture::new_server();
fx.complete_handshake();
fx.peer_goaway(0, H2ErrorCode::NoError);
assert!(
matches!(fx.tick(), Poll::Ready(None)),
"a peer GOAWAY with nothing in flight finishes the driver",
);
let DriverFixture {
driver, mut peer, ..
} = fx;
let waker = noop_waker();
let mut cx = Context::from_waker(&waker);
let mut close = Box::pin(driver.close());
assert!(
close.as_mut().poll(&mut cx).is_pending(),
"close waits for the peer to close its side",
);
assert!(
peer_sees_end_of_stream(&mut peer, &mut cx),
"close shuts down our sending side before it waits",
);
peer.write_all([0; 17]);
assert!(close.as_mut().poll(&mut cx).is_pending());
assert!(
peer.write_side().current(),
"close reads and discards what the peer sends while it waits",
);
peer.shutdown(Shutdown::Write);
assert!(
close.as_mut().poll(&mut cx).is_ready(),
"close finishes once the peer closes its side",
);
}
#[test]
fn close_before_finishing_resets_streams_in_flight() {
let mut fx = DriverFixture::new_server();
fx.complete_handshake();
fx.peer_open_stream(1, Method::Post, "/", false);
let _conn = match fx.tick() {
Poll::Ready(Some(Ok(conn))) => conn,
other => panic!("expected Conn yielded for stream 1, got {other:?}"),
};
let state = fx
.connection
.streams_lock()
.get(&1)
.cloned()
.expect("stream 1 registered");
let DriverFixture { driver, peer, .. } = fx;
let waker = noop_waker();
let mut cx = Context::from_waker(&waker);
let mut close = Box::pin(driver.close());
assert!(close.as_mut().poll(&mut cx).is_pending());
assert!(
state.lifecycle_lock().recv_closed(),
"a stream in flight must be reset when the driver is closed before finishing",
);
peer.shutdown(Shutdown::Write);
assert!(close.as_mut().poll(&mut cx).is_ready());
}
#[test]
fn connection_error_stops_frame_processing() {
let mut fx = DriverFixture::new_server();
fx.complete_handshake();
fx.peer_open_stream(1, Method::Post, "/", false);
let Poll::Ready(Some(Ok(_conn))) = fx.tick() else {
panic!("expected a Conn");
};
fx.next_outbound_frames();
fx.peer_data(9, b"x", false);
let mut frame = [0; ping::ENCODED_LEN];
ping::encode([7; 8], false, &mut frame).expect("encode PING");
fx.peer.write_all(&frame);
let mut frames = Vec::new();
let mut finished = None;
for _ in 0..8 {
let polled = fx.tick();
frames.extend(fx.next_outbound_frames());
if let Poll::Ready(result) = polled {
finished = Some(result);
break;
}
}
assert!(
matches!(
finished,
Some(Some(Err(H2Error::Protocol(H2ErrorCode::ProtocolError))))
),
"{finished:?}"
);
assert_eq!(count_goaways(&frames), 1, "{frames:?}");
assert!(
!frames
.iter()
.any(|frame| matches!(frame, Frame::Ping { ack: true, .. })),
"a frame after the connection error was processed: {frames:?}"
);
}
#[test]
fn connection_error_during_graceful_close_sends_error_goaway() {
let mut fx = DriverFixture::new_server();
fx.complete_handshake();
fx.peer_open_stream(1, Method::Post, "/", false);
let Poll::Ready(Some(Ok(_conn))) = fx.tick() else {
panic!("expected a Conn");
};
fx.peer_goaway(0, H2ErrorCode::NoError);
assert!(fx.tick().is_pending());
fx.peer_open_stream(3, Method::Get, "/", true);
fx.peer_data(9, b"x", false);
let mut frames = Vec::new();
let mut finished = None;
for _ in 0..8 {
let polled = fx.tick();
frames.extend(fx.next_outbound_frames());
if let Poll::Ready(result) = polled {
finished = Some(result);
break;
}
}
assert!(
matches!(
finished,
Some(Some(Err(H2Error::Protocol(H2ErrorCode::ProtocolError))))
),
"{finished:?}"
);
let goaways: Vec<_> = frames
.iter()
.filter_map(|frame| match frame {
Frame::Goaway {
last_stream_id,
error_code,
..
} => Some((*last_stream_id, *error_code)),
_ => None,
})
.collect();
assert_eq!(
goaways,
[(1, H2ErrorCode::NoError), (1, H2ErrorCode::ProtocolError)],
"{frames:?}"
);
}