From 856bf5a33af84317b99ff5e3b8abfe4fcc5465e7 Mon Sep 17 00:00:00 2001 From: jif Date: Thu, 30 Jul 2026 14:10:57 +0000 Subject: [PATCH] Make pending realtime sideband test deterministic (#36166) ## What changed - Replace fixed delays with explicit signals that pause the sideband connection after its HTTP handshake request begins. - Release the handshake after closing the realtime conversation and verify that the connection ends before the handshake completes. - Shut down the test session while rejecting stale realtime errors or duplicate close events from the canceled sideband task. GitOrigin-RevId: 37072842b973fff41ec47235ceb9cfe3cf1a5a37 --- .../core/tests/suite/realtime_conversation.rs | 87 ++++++++++++------- 1 file changed, 55 insertions(+), 32 deletions(-) diff --git a/codex-rs/core/tests/suite/realtime_conversation.rs b/codex-rs/core/tests/suite/realtime_conversation.rs index 569be6a6eb..bd37b35389 100644 --- a/codex-rs/core/tests/suite/realtime_conversation.rs +++ b/codex-rs/core/tests/suite/realtime_conversation.rs @@ -48,6 +48,7 @@ use std::process::Command; use std::sync::Arc; use std::sync::Mutex; use std::time::Duration; +use tokio::io::AsyncReadExt; use tokio::sync::oneshot; use tokio::time::timeout; use uuid::Uuid; @@ -1158,15 +1159,28 @@ async fn conversation_webrtc_close_while_sideband_connecting_drops_pending_join( ) .mount(&server) .await; - let realtime_server = start_websocket_server_with_headers(vec![WebSocketConnectionConfig { - requests: vec![vec![]], - response_headers: Vec::new(), - accept_delay: Some(Duration::from_millis(500)), - close_after_requests: false, - }]) - .await; + let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?; + let realtime_ws_base_url = format!("ws://{}", listener.local_addr()?); + let (pending_handshake_tx, pending_handshake_rx) = oneshot::channel(); + let (release_handshake_tx, release_handshake_rx) = oneshot::channel(); + let handshake_task = tokio::spawn(async move { + let (mut stream, _) = listener.accept().await?; + let mut request = Vec::new(); + while !request.windows(4).any(|window| window == b"\r\n\r\n") { + let bytes_read = stream.read_buf(&mut request).await?; + anyhow::ensure!( + bytes_read > 0, + "sideband closed before sending its handshake" + ); + } + pending_handshake_tx + .send(()) + .expect("pending sideband handshake should have a receiver"); + release_handshake_rx.await?; + stream.read_to_end(&mut request).await?; + Ok::, anyhow::Error>(request) + }); - let realtime_ws_base_url = realtime_server.uri().to_string(); let mut builder = test_codex().with_config(move |config| { config.experimental_realtime_ws_backend_prompt = Some("backend prompt".to_string()); config.experimental_realtime_ws_model = Some("realtime-test-model".to_string()); @@ -1205,10 +1219,9 @@ async fn conversation_webrtc_close_while_sideband_connecting_drops_pending_join( }) .await; assert_eq!(sdp, "v=answer\r\n"); - assert!( - realtime_server.handshakes().is_empty(), - "sideband websocket should still be pending when SDP is emitted" - ); + timeout(Duration::from_secs(10), pending_handshake_rx) + .await + .context("timed out waiting for the sideband handshake")??; test.codex.submit(Op::RealtimeConversationClose).await?; let closed = wait_for_event_match(&test.codex, |msg| match msg { @@ -1218,30 +1231,40 @@ async fn conversation_webrtc_close_while_sideband_connecting_drops_pending_join( .await; assert_eq!(closed.reason.as_deref(), Some("requested")); - let stale_event = timeout(Duration::from_millis(700), async { - wait_for_event_match(&test.codex, |msg| match msg { - EventMsg::RealtimeConversationRealtime(RealtimeConversationRealtimeEvent { - payload: RealtimeEvent::Error(message), - }) => Some(format!("stale realtime error: {message}")), - EventMsg::RealtimeConversationClosed(closed) => { - Some(format!("stale close event: {:?}", closed.reason)) - } - _ => None, - }) + release_handshake_tx + .send(()) + .expect("pending sideband handshake should still be waiting"); + let handshake_request = timeout(Duration::from_secs(10), handshake_task) .await - }) - .await; + .context("timed out waiting for the canceled sideband handshake")???; assert!( - stale_event.is_err(), - "pending sideband task leaked after close: {:?}", - stale_event.ok() - ); - assert!( - realtime_server.handshakes().is_empty(), - "pending sideband task should abort before websocket handshake completes" + handshake_request.starts_with(b"GET "), + "pending sideband connection should close before completing its handshake" ); - realtime_server.shutdown().await; + timeout(Duration::from_secs(10), async { + test.codex.submit(Op::Shutdown).await?; + loop { + match test.codex.next_event().await?.msg { + EventMsg::RealtimeConversationRealtime(RealtimeConversationRealtimeEvent { + payload: RealtimeEvent::Error(message), + }) => { + anyhow::bail!("pending sideband task emitted a stale realtime error: {message}") + } + EventMsg::RealtimeConversationClosed(closed) => { + anyhow::bail!( + "pending sideband task emitted a duplicate close event: {closed:?}" + ) + } + EventMsg::ShutdownComplete => break, + _ => {} + } + } + test.codex.wait_until_terminated().await; + Ok::<(), anyhow::Error>(()) + }) + .await + .context("timed out waiting for realtime session shutdown")??; Ok(()) }