use std::collections::HashMap; use std::sync::Arc; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering; use std::sync::mpsc as std_mpsc; use std::time::Duration; use codex_code_mode_protocol::ExecuteRequest; use codex_code_mode_protocol::FunctionCallOutputContentItem; use pretty_assertions::assert_eq; use serde_json::Value as JsonValue; use tokio::sync::mpsc; use tokio::sync::oneshot; use tokio_util::sync::CancellationToken; use super::*; use crate::session_runtime::OutputItem; struct TestHost; #[derive(Default)] struct RecordingHost { notified: AtomicBool, } impl CellHost for TestHost { async fn invoke_tool( &self, _invocation: CellToolCall, _cancellation_token: CancellationToken, ) -> Result { Err("unexpected tool call".to_string()) } async fn notify( &self, _call_id: String, _text: String, _cancellation_token: CancellationToken, ) -> Result<(), String> { Ok(()) } async fn commit_completion( &self, _stored_value_writes: HashMap, event: CellEvent, cell_state: Arc, ) -> CompletionCommit { cell_state.commit_completion(event, || {}) } async fn closed(&self) {} } impl CellHost for RecordingHost { async fn invoke_tool( &self, _invocation: CellToolCall, _cancellation_token: CancellationToken, ) -> Result { Err("unexpected tool call".to_string()) } async fn notify( &self, _call_id: String, _text: String, _cancellation_token: CancellationToken, ) -> Result<(), String> { self.notified.store(true, Ordering::Release); Ok(()) } async fn commit_completion( &self, _stored_value_writes: HashMap, event: CellEvent, cell_state: Arc, ) -> CompletionCommit { cell_state.commit_completion(event, || {}) } async fn closed(&self) {} } struct CellActorHarness { event_tx: mpsc::UnboundedSender, handle: CellHandle, initial_event_rx: oneshot::Receiver>, task: tokio::task::JoinHandle<()>, runtime_control_rx: std_mpsc::Receiver, _runtime_event_rx: mpsc::UnboundedReceiver, } fn spawn_cell_actor_harness(initial_observe_mode: ObserveMode) -> CellActorHarness { spawn_cell_actor_harness_with_host(initial_observe_mode, Arc::new(TestHost)) } fn spawn_cell_actor_harness_with_host( initial_observe_mode: ObserveMode, host: Arc, ) -> CellActorHarness { let (event_tx, event_rx) = mpsc::unbounded_channel(); let (command_tx, command_rx) = mpsc::unbounded_channel(); let (initial_event_tx, initial_event_rx) = oneshot::channel(); let (runtime_event_tx, runtime_event_rx) = mpsc::unbounded_channel(); let (runtime_tx, _runtime_control_tx, runtime_terminate_handle) = spawn_runtime( HashMap::new(), ExecuteRequest { tool_call_id: "call-1".to_string(), enabled_tools: Vec::new(), source: "await new Promise(() => {});".to_string(), yield_time_ms: None, max_output_tokens: None, }, runtime_event_tx, PendingRuntimeMode::PauseUntilResumed, ) .unwrap(); let (runtime_control_tx, runtime_control_rx) = std_mpsc::channel(); let cell_state = Arc::new(CellState::new(CancellationToken::new())); let handle = CellHandle::new(command_tx, Arc::clone(&cell_state)); let task = tokio::spawn(run_cell( host, CellContext { runtime_tx, runtime_control_tx, runtime_terminate_handle, cell_state, }, event_rx, command_rx, Observer { mode: initial_observe_mode, response_tx: initial_event_tx, }, )); CellActorHarness { event_tx, handle, initial_event_rx, task, runtime_control_rx, _runtime_event_rx: runtime_event_rx, } } async fn wait_for_notification(host: &RecordingHost) { tokio::time::timeout(Duration::from_secs(1), async { while !host.notified.load(Ordering::Acquire) { tokio::task::yield_now().await; } }) .await .expect("notification barrier timed out"); } #[tokio::test] async fn yield_timer_preempts_buffered_runtime_output() { let harness = spawn_cell_actor_harness(ObserveMode::YieldAfter(Duration::ZERO)); harness.event_tx.send(RuntimeEvent::Started).unwrap(); harness .event_tx .send(RuntimeEvent::ContentItem( FunctionCallOutputContentItem::InputText { text: "queued output".to_string(), }, )) .unwrap(); assert_eq!( harness.initial_event_rx.await.unwrap(), Ok(CellEvent::Yielded { content_items: Vec::new(), }) ); let termination = harness.handle.terminate(); drop(harness.event_tx); assert_eq!( termination.await, Ok(CellEvent::Terminated { content_items: vec![OutputItem::Text { text: "queued output".to_string(), }], }) ); harness.task.await.unwrap(); } #[tokio::test] async fn queued_termination_preempts_unobserved_runtime_completion() { let harness = spawn_cell_actor_harness(ObserveMode::YieldAfter(Duration::from_secs(60))); harness .event_tx .send(RuntimeEvent::Result { stored_value_writes: HashMap::new(), error_text: None, }) .unwrap(); let termination = harness.handle.terminate(); let terminated = Ok(CellEvent::Terminated { content_items: Vec::new(), }); assert_eq!(termination.await, terminated.clone()); assert_eq!(harness.initial_event_rx.await.unwrap(), terminated); harness.task.await.unwrap(); } #[tokio::test] async fn observation_dropped_before_dequeue_does_not_consume_output() { let host = Arc::new(RecordingHost::default()); let harness = spawn_cell_actor_harness_with_host( ObserveMode::YieldAfter(Duration::from_secs(60)), Arc::clone(&host), ); harness.event_tx.send(RuntimeEvent::YieldRequested).unwrap(); assert!(harness.initial_event_rx.await.unwrap().is_ok()); drop( harness .handle .observe(ObserveMode::YieldAfter(Duration::from_secs(60))), ); harness .event_tx .send(RuntimeEvent::ContentItem( FunctionCallOutputContentItem::InputText { text: "survives pre-dequeue cancellation".to_string(), }, )) .unwrap(); harness.event_tx.send(RuntimeEvent::YieldRequested).unwrap(); harness .event_tx .send(RuntimeEvent::Notify { call_id: "after-dropped-command".to_string(), text: "barrier".to_string(), }) .unwrap(); wait_for_notification(&host).await; assert_eq!( harness .handle .observe(ObserveMode::YieldAfter(Duration::ZERO)) .await, Ok(CellEvent::Yielded { content_items: vec![OutputItem::Text { text: "survives pre-dequeue cancellation".to_string(), }], }) ); let termination = harness.handle.terminate(); drop(harness.event_tx); assert_eq!( termination.await, Ok(CellEvent::Terminated { content_items: Vec::new(), }) ); harness.task.await.unwrap(); } #[tokio::test] async fn dropped_yield_observer_preserves_output_for_the_next_observation() { let host = Arc::new(RecordingHost::default()); let harness = spawn_cell_actor_harness_with_host( ObserveMode::YieldAfter(Duration::from_secs(60)), Arc::clone(&host), ); harness.event_tx.send(RuntimeEvent::YieldRequested).unwrap(); assert!(harness.initial_event_rx.await.unwrap().is_ok()); let dropped_observation = harness .handle .observe(ObserveMode::YieldAfter(Duration::from_secs(60))); assert_eq!( harness .handle .observe(ObserveMode::YieldAfter(Duration::ZERO)) .await, Err(CellError::Busy) ); drop(dropped_observation); harness .event_tx .send(RuntimeEvent::ContentItem( FunctionCallOutputContentItem::InputText { text: "survives active cancellation".to_string(), }, )) .unwrap(); harness.event_tx.send(RuntimeEvent::YieldRequested).unwrap(); harness .event_tx .send(RuntimeEvent::Notify { call_id: "after-dropped-observer".to_string(), text: "barrier".to_string(), }) .unwrap(); wait_for_notification(&host).await; assert_eq!( harness .handle .observe(ObserveMode::YieldAfter(Duration::ZERO)) .await, Ok(CellEvent::Yielded { content_items: vec![OutputItem::Text { text: "survives active cancellation".to_string(), }], }) ); let termination = harness.handle.terminate(); drop(harness.event_tx); assert_eq!( termination.await, Ok(CellEvent::Terminated { content_items: Vec::new(), }) ); harness.task.await.unwrap(); } #[tokio::test] async fn dropped_pending_observer_preserves_the_frontier_for_the_next_observation() { let host = Arc::new(RecordingHost::default()); let harness = spawn_cell_actor_harness_with_host( ObserveMode::YieldAfter(Duration::from_secs(60)), Arc::clone(&host), ); harness.event_tx.send(RuntimeEvent::YieldRequested).unwrap(); assert!(harness.initial_event_rx.await.unwrap().is_ok()); let dropped_observation = harness.handle.observe(ObserveMode::PendingFrontier); assert_eq!( harness.handle.observe(ObserveMode::PendingFrontier).await, Err(CellError::Busy) ); drop(dropped_observation); harness .event_tx .send(RuntimeEvent::ToolCall { id: "tool-1".to_string(), name: codex_protocol::ToolName { name: "echo".to_string(), namespace: None, }, kind: codex_code_mode_protocol::CodeModeToolKind::Function, input: Some(serde_json::json!({})), }) .unwrap(); harness.event_tx.send(RuntimeEvent::Pending).unwrap(); harness .event_tx .send(RuntimeEvent::Notify { call_id: "after-dropped-pending".to_string(), text: "barrier".to_string(), }) .unwrap(); wait_for_notification(&host).await; assert_eq!( harness.handle.observe(ObserveMode::PendingFrontier).await, Ok(CellEvent::Pending { content_items: Vec::new(), pending_tool_call_ids: vec!["tool-1".to_string()], }) ); assert!(matches!( harness.runtime_control_rx.try_recv(), Err(std_mpsc::TryRecvError::Empty) )); let termination = harness.handle.terminate(); drop(harness.event_tx); assert_eq!( termination.await, Ok(CellEvent::Terminated { content_items: Vec::new(), }) ); harness.task.await.unwrap(); } #[tokio::test] async fn only_the_first_termination_claims_a_buffered_completion() { let cell_state = CellState::new(CancellationToken::new()); let completion = CellEvent::Completed { content_items: Vec::new(), error_text: None, }; assert_eq!( cell_state.commit_completion(completion.clone(), || {}), CompletionCommit::Committed ); assert!(matches!( cell_state.deliver_completion(/*response_tx*/ None), CompletionDelivery::Buffered )); let first_termination = cell_state.request_termination(); assert_eq!( cell_state.request_termination().await, Err(CellError::AlreadyTerminating) ); assert_eq!(first_termination.await, Ok(completion.clone())); assert_eq!( cell_state.finish_termination(CellEvent::Terminated { content_items: Vec::new(), }), Some(completion) ); } #[tokio::test] async fn termination_claim_prevents_stored_value_commit() { let cell_state = CellState::new(CancellationToken::new()); let termination = cell_state.request_termination(); let mut commit_ran = false; let completion = CellEvent::Completed { content_items: Vec::new(), error_text: None, }; assert_eq!( cell_state.commit_completion(completion.clone(), || commit_ran = true), CompletionCommit::Rejected(completion) ); assert!(!commit_ran); let terminated = CellEvent::Terminated { content_items: Vec::new(), }; assert_eq!( cell_state.finish_termination(terminated.clone()), Some(terminated.clone()) ); assert_eq!(termination.await, Ok(terminated)); } #[test] fn failed_completion_delivery_rebuffers_the_event() { let cell_state = CellState::new(CancellationToken::new()); let event = CellEvent::Completed { content_items: Vec::new(), error_text: None, }; assert_eq!( cell_state.commit_completion(event.clone(), || {}), CompletionCommit::Committed ); let (response_tx, response_rx) = oneshot::channel(); drop(response_rx); assert!(matches!( cell_state.deliver_completion(Some(response_tx)), CompletionDelivery::Buffered )); assert!(cell_state.accepting_observations()); let (response_tx, mut response_rx) = oneshot::channel(); assert!(matches!( cell_state.route_observation(response_tx), ObservationDelivery::Delivered )); assert_eq!(response_rx.try_recv(), Ok(Ok(event))); }