0.3.4-alpha.5

This commit is contained in:
2026-09-21 18:22:19 +02:00
parent fa17ea0ca5
commit 74cdb177e9
8 changed files with 358 additions and 10 deletions

View File

@@ -0,0 +1,21 @@
# file: crates/apps/game-realtime-websocket-smoke/Cargo.toml
# version: 1
[package]
name = "game-realtime-websocket-smoke"
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
authors.workspace = true
publish.workspace = true
[dependencies]
game-logging-lib = { path = "../../common/game-logging-lib" }
game-realtime-transport-lib = { path = "../../common/game-realtime-transport-lib" }
game-realtime-websocket-lib = { path = "../../common/game-realtime-websocket-lib" }
tokio = { workspace = true, features = ["macros", "rt", "time"] }
tracing.workspace = true
[lints]
workspace = true

View File

@@ -0,0 +1,108 @@
// file: crates/apps/game-realtime-websocket-smoke/src/main.rs
// version: 1
#![warn(missing_docs)]
#![deny(unreachable_pub)]
#![forbid(unsafe_code)]
//! Executable localhost smoke for the public games.sasedev WebSocket realtime transport path.
use game_realtime_transport_lib::RealtimeConnection; // rust-rules: trait-import
use game_realtime_transport_lib::RealtimeReceiver; // rust-rules: trait-import
use game_realtime_transport_lib::RealtimeSender; // rust-rules: trait-import
const CLIENT_PAYLOAD: &[u8] = b"games.sasedev-client-smoke";
const SERVER_PAYLOAD: &[u8] = b"games.sasedev-server-smoke";
const SMOKE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5);
const TRACING_TARGET: &str = "games::realtime::websocket::smoke";
#[tokio::main(flavor = "current_thread")]
async fn main() -> std::process::ExitCode {
let _logging_guard = match game_logging_lib::init_console_tracing() {
std::result::Result::Ok(guard) => guard,
std::result::Result::Err(error) => {
eprintln!("failed to initialize realtime WebSocket smoke tracing: {error}");
return std::process::ExitCode::FAILURE;
},
};
tracing::info!(target: TRACING_TARGET, "realtime WebSocket smoke started");
let result = tokio::time::timeout(SMOKE_TIMEOUT, run_smoke()).await;
return match result {
std::result::Result::Ok(std::result::Result::Ok(())) => {
tracing::info!(target: TRACING_TARGET, "realtime WebSocket smoke passed");
println!("game-realtime-websocket-smoke: PASS");
std::process::ExitCode::SUCCESS
},
std::result::Result::Ok(std::result::Result::Err(error)) => {
tracing::error!(target: TRACING_TARGET, detail = error.as_str(), "realtime WebSocket smoke failed");
eprintln!("game-realtime-websocket-smoke: FAIL: {error}");
std::process::ExitCode::FAILURE
},
std::result::Result::Err(_) => {
tracing::error!(target: TRACING_TARGET, timeout_ms = SMOKE_TIMEOUT.as_millis(), "realtime WebSocket smoke timed out");
eprintln!("game-realtime-websocket-smoke: FAIL: smoke timed out");
std::process::ExitCode::FAILURE
},
};
}
async fn run_smoke() -> std::result::Result<(), String> {
let bind_address = std::net::SocketAddr::from(([127, 0, 0, 1], 0));
let listener = match game_realtime_websocket_lib::WebSocketListener::bind(bind_address).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(format!("listener bind failed: {error}")),
};
let endpoint = format!("ws://{}/", listener.local_addr());
tracing::info!(target: TRACING_TARGET, endpoint = endpoint.as_str(), "loopback endpoint bound");
let (server_result, client_result) = tokio::join!(listener.accept(), game_realtime_websocket_lib::connect(endpoint.as_str()));
let server_connection = match server_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(format!("server accept failed: {error}")),
};
let client_connection = match client_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(format!("client connect failed: {error}")),
};
let (mut server_sender, mut server_receiver) = server_connection.split();
let (mut client_sender, mut client_receiver) = client_connection.split();
let client_message = game_realtime_transport_lib::TransportMessage::new(CLIENT_PAYLOAD.to_vec());
if let std::result::Result::Err(error) = client_sender.send(client_message).await {
return std::result::Result::Err(format!("client send failed: {error}"));
}
let server_received = match server_receiver.receive().await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(format!("server receive failed: {error}")),
};
if !receive_matches(server_received, CLIENT_PAYLOAD) {
return std::result::Result::Err(String::from("server did not receive the expected client payload"));
}
let server_message = game_realtime_transport_lib::TransportMessage::new(SERVER_PAYLOAD.to_vec());
if let std::result::Result::Err(error) = server_sender.send(server_message).await {
return std::result::Result::Err(format!("server send failed: {error}"));
}
let client_received = match client_receiver.receive().await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(format!("client receive failed: {error}")),
};
if !receive_matches(client_received, SERVER_PAYLOAD) {
return std::result::Result::Err(String::from("client did not receive the expected server payload"));
}
if let std::result::Result::Err(error) = client_sender.close().await {
return std::result::Result::Err(format!("client close failed: {error}"));
}
let server_close = match server_receiver.receive().await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(format!("server close observation failed: {error}")),
};
if server_close != game_realtime_transport_lib::TransportReceive::Closed {
return std::result::Result::Err(String::from("server did not observe the client close handshake"));
}
return std::result::Result::Ok(());
}
fn receive_matches(receive: game_realtime_transport_lib::TransportReceive, expected: &[u8]) -> bool {
return match receive {
game_realtime_transport_lib::TransportReceive::Message(message) => message.as_bytes() == expected,
game_realtime_transport_lib::TransportReceive::Closed => false,
};
}