0.3.5-alpha.4

This commit is contained in:
2026-09-21 23:37:18 +02:00
parent fd6ffecdf5
commit ce3811f8ec
14 changed files with 1284 additions and 147 deletions

View 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"),
};
}