0.3.5-alpha.4
This commit is contained in:
146
crates/common/game-realtime-webtransport-lib/tests/robustness.rs
Normal file
146
crates/common/game-realtime-webtransport-lib/tests/robustness.rs
Normal file
@@ -0,0 +1,146 @@
|
||||
// file: crates/common/game-realtime-webtransport-lib/tests/robustness.rs
|
||||
// version: 2
|
||||
|
||||
//! Negative and bounded lifecycle tests for the native reliable WebTransport backend.
|
||||
|
||||
const SHORT_OPERATION_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(50);
|
||||
const SMALL_MESSAGE_LIMIT: usize = 32;
|
||||
const TEST_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(3);
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn outbound_payload_over_the_configured_limit_is_rejected_before_write() {
|
||||
let config = game_realtime_webtransport_lib::WebTransportConfig::default().with_max_message_size(SMALL_MESSAGE_LIMIT);
|
||||
let (_server_connection, client_connection) = establish_backend_pair(config, config).await;
|
||||
let (mut client_sender, _client_receiver) = game_realtime_transport_lib::RealtimeConnection::split(client_connection);
|
||||
let oversized = game_realtime_transport_lib::TransportMessage::new(vec![7; SMALL_MESSAGE_LIMIT + 1]);
|
||||
let result = game_realtime_transport_lib::RealtimeSender::send(&mut client_sender, oversized).await;
|
||||
match result {
|
||||
Ok(()) => panic!("oversized outbound WebTransport payload was accepted"),
|
||||
Err(error) => assert_eq!(error.kind(), game_realtime_transport_lib::TransportErrorKind::MessageTooLarge),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn inbound_payload_over_the_configured_limit_is_rejected_before_allocation() {
|
||||
let server_config = game_realtime_webtransport_lib::WebTransportConfig::default().with_max_message_size(SMALL_MESSAGE_LIMIT);
|
||||
let client_config = game_realtime_webtransport_lib::WebTransportConfig::default().with_max_message_size(SMALL_MESSAGE_LIMIT + 1);
|
||||
let (server_connection, client_connection) = establish_backend_pair(server_config, client_config).await;
|
||||
let (_server_sender, mut server_receiver) = game_realtime_transport_lib::RealtimeConnection::split(server_connection);
|
||||
let (mut client_sender, _client_receiver) = game_realtime_transport_lib::RealtimeConnection::split(client_connection);
|
||||
let payload = game_realtime_transport_lib::TransportMessage::new(vec![3; SMALL_MESSAGE_LIMIT + 1]);
|
||||
if let Err(error) = game_realtime_transport_lib::RealtimeSender::send(&mut client_sender, payload).await {
|
||||
panic!("client failed to send payload allowed by its local bound: {error}");
|
||||
}
|
||||
let receive = tokio::time::timeout(TEST_TIMEOUT, game_realtime_transport_lib::RealtimeReceiver::receive(&mut server_receiver)).await;
|
||||
match receive {
|
||||
Ok(Ok(value)) => panic!("oversized inbound WebTransport payload produced a successful receive: {value:?}"),
|
||||
Ok(Err(error)) => assert_eq!(error.kind(), game_realtime_transport_lib::TransportErrorKind::MessageTooLarge),
|
||||
Err(_) => panic!("oversized inbound WebTransport payload did not complete within the test timeout"),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn explicit_sender_abort_is_observed_as_aborted_receive() {
|
||||
let config = game_realtime_webtransport_lib::WebTransportConfig::default();
|
||||
let (server_connection, client_connection) = establish_backend_pair(config, config).await;
|
||||
let (_server_sender, mut server_receiver) = game_realtime_transport_lib::RealtimeConnection::split(server_connection);
|
||||
let (mut client_sender, _client_receiver) = game_realtime_transport_lib::RealtimeConnection::split(client_connection);
|
||||
if let Err(error) = client_sender.abort(0x41) {
|
||||
panic!("client sender abort failed: {error}");
|
||||
}
|
||||
let receive = tokio::time::timeout(TEST_TIMEOUT, game_realtime_transport_lib::RealtimeReceiver::receive(&mut server_receiver)).await;
|
||||
match receive {
|
||||
Ok(Ok(value)) => panic!("reset WebTransport stream produced a successful receive: {value:?}"),
|
||||
Ok(Err(error)) => assert_eq!(error.kind(), game_realtime_transport_lib::TransportErrorKind::Aborted),
|
||||
Err(_) => panic!("peer reset was not observed within the test timeout"),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn explicit_receiver_abort_makes_the_local_receive_half_terminal() {
|
||||
let config = game_realtime_webtransport_lib::WebTransportConfig::default();
|
||||
let (server_connection, _client_connection) = establish_backend_pair(config, config).await;
|
||||
let (_server_sender, mut server_receiver) = game_realtime_transport_lib::RealtimeConnection::split(server_connection);
|
||||
if let Err(error) = server_receiver.abort(0x42) {
|
||||
panic!("server receiver abort failed: {error}");
|
||||
}
|
||||
let result = game_realtime_transport_lib::RealtimeReceiver::receive(&mut server_receiver).await;
|
||||
match result {
|
||||
Ok(value) => panic!("aborted WebTransport receiver produced a successful receive: {value:?}"),
|
||||
Err(error) => assert_eq!(error.kind(), game_realtime_transport_lib::TransportErrorKind::Aborted),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn dropping_sender_without_close_resets_the_stream_instead_of_synthesizing_fin() {
|
||||
let config = game_realtime_webtransport_lib::WebTransportConfig::default();
|
||||
let (server_connection, client_connection) = establish_backend_pair(config, config).await;
|
||||
let (_server_sender, mut server_receiver) = game_realtime_transport_lib::RealtimeConnection::split(server_connection);
|
||||
let (client_sender, _client_receiver) = game_realtime_transport_lib::RealtimeConnection::split(client_connection);
|
||||
drop(client_sender);
|
||||
let receive = tokio::time::timeout(TEST_TIMEOUT, game_realtime_transport_lib::RealtimeReceiver::receive(&mut server_receiver)).await;
|
||||
match receive {
|
||||
Ok(Ok(value)) => panic!("dropped WebTransport sender produced a clean receive result: {value:?}"),
|
||||
Ok(Err(error)) => assert_eq!(error.kind(), game_realtime_transport_lib::TransportErrorKind::Aborted),
|
||||
Err(_) => panic!("sender drop reset was not observed within the test timeout"),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn primary_stream_accept_honors_the_configured_deadline() {
|
||||
let config = game_realtime_webtransport_lib::WebTransportConfig::default().with_primary_stream_timeout(SHORT_OPERATION_TIMEOUT);
|
||||
let (server_session, _client_session) = establish_sessions(config, config).await;
|
||||
let result = server_session.accept_primary_connection().await;
|
||||
match result {
|
||||
Ok(_) => panic!("primary stream accept unexpectedly succeeded without a peer-created stream"),
|
||||
Err(error) => assert_eq!(error.kind(), game_realtime_transport_lib::TransportErrorKind::Timeout),
|
||||
}
|
||||
}
|
||||
|
||||
async fn establish_backend_pair(
|
||||
server_transport: game_realtime_webtransport_lib::WebTransportConfig,
|
||||
client_transport: game_realtime_webtransport_lib::WebTransportConfig,
|
||||
) -> (game_realtime_webtransport_lib::WebTransportConnection, game_realtime_webtransport_lib::WebTransportConnection) {
|
||||
let (server_session, client_session) = establish_sessions(server_transport, client_transport).await;
|
||||
let client_connection = match client_session.open_primary_connection().await {
|
||||
Ok(value) => value,
|
||||
Err(error) => panic!("client primary stream open failed: {error}"),
|
||||
};
|
||||
let server_connection = match server_session.accept_primary_connection().await {
|
||||
Ok(value) => value,
|
||||
Err(error) => panic!("server primary stream accept failed: {error}"),
|
||||
};
|
||||
return (server_connection, client_connection);
|
||||
}
|
||||
|
||||
async fn establish_sessions(
|
||||
server_transport: game_realtime_webtransport_lib::WebTransportConfig,
|
||||
client_transport: game_realtime_webtransport_lib::WebTransportConfig,
|
||||
) -> (game_realtime_webtransport_lib::WebTransportSession, game_realtime_webtransport_lib::WebTransportSession) {
|
||||
let identity = match game_realtime_webtransport_lib::WebTransportServerIdentity::generate_loopback() {
|
||||
Ok(value) => value,
|
||||
Err(error) => panic!("loopback identity generation failed: {error}"),
|
||||
};
|
||||
let certificate_hash = identity.certificate_hash().clone();
|
||||
let server_config = game_realtime_webtransport_lib::WebTransportServerConfig::new(std::net::SocketAddr::from(([127, 0, 0, 1], 0)), identity)
|
||||
.with_transport_config(server_transport);
|
||||
let mut listener = match game_realtime_webtransport_lib::WebTransportListener::bind(server_config) {
|
||||
Ok(value) => value,
|
||||
Err(error) => panic!("WebTransport listener bind failed: {error}"),
|
||||
};
|
||||
let endpoint = format!("https://{}/robustness", listener.local_addr());
|
||||
let client_config = match game_realtime_webtransport_lib::WebTransportClientConfig::new(endpoint.as_str(), certificate_hash) {
|
||||
Ok(value) => value.with_transport_config(client_transport),
|
||||
Err(error) => panic!("WebTransport client configuration failed: {error}"),
|
||||
};
|
||||
let sessions = tokio::time::timeout(TEST_TIMEOUT, async {
|
||||
return tokio::join!(listener.accept(), game_realtime_webtransport_lib::connect(&client_config));
|
||||
})
|
||||
.await;
|
||||
return match sessions {
|
||||
Ok((Ok(server), Ok(client))) => (server, client),
|
||||
Ok((Err(error), _)) => panic!("WebTransport server establishment failed: {error}"),
|
||||
Ok((_, Err(error))) => panic!("WebTransport client establishment failed: {error}"),
|
||||
Err(_) => panic!("WebTransport loopback establishment timed out"),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user