Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 91 additions & 83 deletions codex-rs/app-server/src/message_processor_schedule_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ use codex_rollout::state_db::StateDbHandle;
use core_test_support::responses;
use pretty_assertions::assert_eq;
use std::collections::BTreeMap;
use std::collections::VecDeque;
use std::future::Future;
use std::path::Path;
use std::sync::Arc;
Expand All @@ -117,6 +118,7 @@ struct ScheduleHarness {
state_db: StateDbHandle,
processor: Arc<MessageProcessor>,
outgoing_rx: mpsc::Receiver<OutgoingEnvelope>,
pending_notifications: VecDeque<ServerNotification>,
session: Arc<ConnectionSessionState>,
next_request_id: i64,
}
Expand Down Expand Up @@ -153,6 +155,7 @@ impl ScheduleHarness {
state_db,
processor,
outgoing_rx,
pending_notifications: VecDeque::new(),
session: Arc::new(ConnectionSessionState::new(ConnectionOrigin::WebSocket)),
next_request_id: 1,
};
Expand Down Expand Up @@ -250,6 +253,7 @@ impl ScheduleHarness {
params: ThreadStartParams {
cwd: Some(self.workspace_cwd()),
ephemeral: Some(ephemeral),
auth_profile: Some(None),
..ThreadStartParams::default()
},
})
Expand Down Expand Up @@ -302,17 +306,9 @@ impl ScheduleHarness {
.await
.expect("timed out waiting for response")
.expect("outgoing channel closed");
let OutgoingEnvelope::ToConnection {
connection_id,
message,
..
} = envelope
else {
let Some(message) = message_for_test_connection(envelope) else {
continue;
};
if connection_id != TEST_CONNECTION_ID {
continue;
}
match message {
OutgoingMessage::Response(response)
if response.id == RequestId::Integer(request_id) =>
Expand All @@ -323,6 +319,9 @@ impl ScheduleHarness {
OutgoingMessage::Error(error) if error.id == RequestId::Integer(request_id) => {
panic!("request {request_id} failed: {:?}", error.error);
}
OutgoingMessage::AppServerNotification(notification) => {
self.pending_notifications.push_back(notification);
}
_ => {
continue;
}
Expand All @@ -337,17 +336,9 @@ impl ScheduleHarness {
.await
.expect("timed out waiting for error")
.expect("outgoing channel closed");
let OutgoingEnvelope::ToConnection {
connection_id,
message,
..
} = envelope
else {
let Some(message) = message_for_test_connection(envelope) else {
continue;
};
if connection_id != TEST_CONNECTION_ID {
continue;
}
match message {
OutgoingMessage::Response(response)
if response.id == RequestId::Integer(request_id) =>
Expand All @@ -360,6 +351,9 @@ impl ScheduleHarness {
OutgoingMessage::Error(error) if error.id == RequestId::Integer(request_id) => {
return error.error;
}
OutgoingMessage::AppServerNotification(notification) => {
self.pending_notifications.push_back(notification);
}
_ => {
continue;
}
Expand Down Expand Up @@ -423,6 +417,43 @@ impl ScheduleHarness {
response.schedule
}

async fn seed_schedule_failure(&self, schedule_id: &str) -> Result<()> {
let now = Utc::now();
let local_active_fresh_after = self
.processor
.thread_schedule_runtime
.local_active_fresh_after(now);
let claim = self
.state_db
.thread_schedules()
.claim_thread_schedule_now_with_params(codex_state::ThreadScheduleNowClaimParams {
schedule_id,
now,
lease_id: "lease-fail",
lease_duration: std::time::Duration::from_secs(300),
local_active_owner_id: Some(
self.processor
.thread_schedule_runtime
.local_active_owner_id(),
),
local_active_fresh_after: Some(local_active_fresh_after),
})
.await?
.expect("schedule should claim for seeded failure");
self.state_db
.thread_schedules()
.fail_thread_schedule_run(
schedule_id,
claim.run.run_id.as_str(),
claim.run.lease_id.as_str(),
now,
/*next_run_at*/ None,
"model unavailable".to_string(),
)
.await?;
Ok(())
}

async fn read_schedule_deleted(&mut self, thread_id: &str, schedule_id: &str) {
loop {
let notification = self.read_server_notification().await;
Expand Down Expand Up @@ -706,6 +737,9 @@ impl ScheduleHarness {
}

async fn read_server_notification(&mut self) -> ServerNotification {
if let Some(notification) = self.pending_notifications.pop_front() {
return notification;
}
loop {
let envelope = tokio::time::timeout(
std::time::Duration::from_secs(/*secs*/ 20),
Expand All @@ -714,18 +748,8 @@ impl ScheduleHarness {
.await
.expect("timed out waiting for server notification")
.expect("outgoing channel closed");
let message = match envelope {
OutgoingEnvelope::ToConnection {
connection_id,
message,
..
} => {
if connection_id != TEST_CONNECTION_ID {
continue;
}
message
}
OutgoingEnvelope::Broadcast { message } => message,
let Some(message) = message_for_test_connection(envelope) else {
continue;
};
if let OutgoingMessage::AppServerNotification(notification) = message {
return notification;
Expand All @@ -734,6 +758,17 @@ impl ScheduleHarness {
}
}

fn message_for_test_connection(envelope: OutgoingEnvelope) -> Option<OutgoingMessage> {
match envelope {
OutgoingEnvelope::ToConnection {
connection_id,
message,
..
} => (connection_id == TEST_CONNECTION_ID).then_some(message),
OutgoingEnvelope::Broadcast { message } => Some(message),
}
}

async fn create_mock_responses_server_unauthorized() -> MockServer {
let server = MockServer::start().await;
Mock::given(method("POST"))
Expand All @@ -759,7 +794,10 @@ where
.name("schedule-harness".to_string())
.stack_size(16 * 1024 * 1024)
.spawn(|| {
tokio::runtime::Builder::new_current_thread()
// Message processing spawns runtime work that must remain runnable
// while Windows filesystem and SQLite operations block the caller.
tokio::runtime::Builder::new_multi_thread()
.worker_threads(2)
.enable_all()
.build()
.expect("schedule harness runtime should build")
Expand Down Expand Up @@ -1191,28 +1229,8 @@ fn thread_schedule_resume_recomputes_recurring_without_next_run_at() -> Result<(
.await;
harness.read_schedule_updated(&thread_id).await;

let claim = harness
.state_db
.thread_schedules()
.claim_thread_schedule_now(
create_response.schedule.schedule_id.as_str(),
Utc::now(),
"lease-fail",
std::time::Duration::from_secs(300),
)
.await?
.expect("schedule should claim for seeded failure");
harness
.state_db
.thread_schedules()
.fail_thread_schedule_run(
create_response.schedule.schedule_id.as_str(),
claim.run.run_id.as_str(),
"lease-fail",
Utc::now(),
/*next_run_at*/ None,
"model unavailable".to_string(),
)
.seed_schedule_failure(create_response.schedule.schedule_id.as_str())
.await?;
let failed_schedule = harness
.state_db
Expand Down Expand Up @@ -1281,28 +1299,8 @@ fn thread_schedule_update_to_active_resets_failure_count() -> Result<()> {
.await;
harness.read_schedule_updated(&thread_id).await;

let claim = harness
.state_db
.thread_schedules()
.claim_thread_schedule_now(
create_response.schedule.schedule_id.as_str(),
Utc::now(),
"lease-fail",
std::time::Duration::from_secs(300),
)
.await?
.expect("schedule should claim for seeded failure");
harness
.state_db
.thread_schedules()
.fail_thread_schedule_run(
create_response.schedule.schedule_id.as_str(),
claim.run.run_id.as_str(),
"lease-fail",
Utc::now(),
/*next_run_at*/ None,
"model unavailable".to_string(),
)
.seed_schedule_failure(create_response.schedule.schedule_id.as_str())
.await?;
let failed_schedule = harness
.state_db
Expand Down Expand Up @@ -1788,45 +1786,50 @@ fn thread_schedule_create_nests_loops_to_depth_five() -> Result<()> {
let thread_id = thread.thread.id.clone();

let root = harness
.create_interval_thread_schedule(&thread_id, "root loop", 1, None)
.create_interval_thread_schedule(
&thread_id,
"root loop",
/*amount_minutes*/ 1,
/*parent_schedule_id*/ None,
)
.await;
let level_2 = harness
.create_interval_thread_schedule(
&thread_id,
"level 2 loop",
2,
/*amount_minutes*/ 2,
Some(root.schedule_id.clone()),
)
.await;
let branch = harness
.create_interval_thread_schedule(
&thread_id,
"branch level 2 loop",
3,
/*amount_minutes*/ 3,
Some(root.schedule_id.clone()),
)
.await;
let level_3 = harness
.create_interval_thread_schedule(
&thread_id,
"level 3 loop",
3,
/*amount_minutes*/ 3,
Some(level_2.schedule_id.clone()),
)
.await;
let level_4 = harness
.create_interval_thread_schedule(
&thread_id,
"level 4 loop",
4,
/*amount_minutes*/ 4,
Some(level_3.schedule_id.clone()),
)
.await;
let level_5 = harness
.create_interval_thread_schedule(
&thread_id,
"level 5 loop",
5,
/*amount_minutes*/ 5,
Some(level_4.schedule_id.clone()),
)
.await;
Expand Down Expand Up @@ -1889,21 +1892,26 @@ fn thread_schedule_delete_parent_emits_descendant_delete_notifications() -> Resu
let thread = harness.start_materialized_thread().await;
let thread_id = thread.thread.id.clone();
let root = harness
.create_interval_thread_schedule(&thread_id, "root loop", 1, None)
.create_interval_thread_schedule(
&thread_id,
"root loop",
/*amount_minutes*/ 1,
/*parent_schedule_id*/ None,
)
.await;
let child = harness
.create_interval_thread_schedule(
&thread_id,
"child loop",
2,
/*amount_minutes*/ 2,
Some(root.schedule_id.clone()),
)
.await;
let grandchild = harness
.create_interval_thread_schedule(
&thread_id,
"grandchild loop",
3,
/*amount_minutes*/ 3,
Some(child.schedule_id.clone()),
)
.await;
Expand Down Expand Up @@ -2300,7 +2308,7 @@ fn schedule_create_materializes_fresh_thread_rollout_before_first_user_turn() ->
let rollout_path = codex_rollout::find_thread_path_by_id_str(
harness._codex_home.path(),
&thread_id,
Option::<&codex_state::StateRuntime>::None,
/*state_db_ctx*/ Option::<&codex_state::StateRuntime>::None,
)
.await?
.expect("fresh scheduled thread should have a materialized rollout");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,10 @@ mod tests {
assert_eq!(named_api_peer.auth_profile.as_deref(), Some("work"));
assert_eq!(named_api_peer.auth_profile_kind, AuthProfileKind::Named);

let default_api_peer = api_active_session_peer(test_active_peer(ThreadId::new(), None));
let default_api_peer = api_active_session_peer(test_active_peer(
ThreadId::new(),
/*auth_profile*/ None,
));

assert_eq!(default_api_peer.auth_profile, None);
assert_eq!(default_api_peer.auth_profile_kind, AuthProfileKind::Default);
Expand Down Expand Up @@ -682,7 +685,7 @@ mod tests {
auth_profile,
process: None,
capabilities: ActivePeerCapabilities::codewith_session(),
last_seen_at: LastSeenAt::from_unix_seconds(100),
last_seen_at: LastSeenAt::from_unix_seconds(/*seconds*/ 100),
}
}
}
Loading
Loading