mirror of
https://github.com/openai/codex.git
synced 2026-09-16 12:13:30 +00:00
test
This commit is contained in:
115
codex-rs/core/tests/suite/parallel_tool_calls.rs
Normal file
115
codex-rs/core/tests/suite/parallel_tool_calls.rs
Normal file
@@ -0,0 +1,115 @@
|
||||
#![cfg(unix)]
|
||||
|
||||
use std::time::Instant;
|
||||
|
||||
use codex_core::built_in_model_providers;
|
||||
use codex_core::protocol::EventMsg;
|
||||
use codex_core::ConversationManager;
|
||||
use codex_login::CodexAuth;
|
||||
use core_test_support::load_sse_fixture_with_id_from_str;
|
||||
use tempfile::TempDir;
|
||||
|
||||
fn build_parallel_exec_sse() -> String {
|
||||
let item1 = serde_json::json!({
|
||||
"type": "response.output_item.done",
|
||||
"item": {
|
||||
"type": "local_shell_call",
|
||||
"call_id": "c1",
|
||||
"status": "in_progress",
|
||||
"action": {
|
||||
"type": "exec",
|
||||
"command": ["/bin/sh", "-c", "echo A"],
|
||||
"timeout_ms": 3000,
|
||||
"working_directory": null,
|
||||
"env": null,
|
||||
"user": null
|
||||
}
|
||||
}
|
||||
});
|
||||
let item2 = serde_json::json!({
|
||||
"type": "response.output_item.done",
|
||||
"item": {
|
||||
"type": "local_shell_call",
|
||||
"call_id": "c2",
|
||||
"status": "in_progress",
|
||||
"action": {
|
||||
"type": "exec",
|
||||
"command": ["/bin/sh", "-c", "echo B"],
|
||||
"timeout_ms": 3000,
|
||||
"working_directory": null,
|
||||
"env": null,
|
||||
"user": null
|
||||
}
|
||||
}
|
||||
});
|
||||
let completed = serde_json::json!({
|
||||
"type": "response.completed",
|
||||
"response": { "id": "__ID__" }
|
||||
});
|
||||
|
||||
let raw = serde_json::json!([
|
||||
item1, item2, completed
|
||||
])
|
||||
.to_string();
|
||||
load_sse_fixture_with_id_from_str(&raw, "resp_parallel")
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn local_shell_calls_begin_before_any_end_and_run_concurrently() {
|
||||
let tmp_sse = tempfile::NamedTempFile::new().expect("tmp sse");
|
||||
std::fs::write(tmp_sse.path(), build_parallel_exec_sse()).expect("write sse");
|
||||
std::env::set_var("CODEX_RS_SSE_FIXTURE", tmp_sse.path());
|
||||
|
||||
let home = TempDir::new().unwrap();
|
||||
let mut config = core_test_support::load_default_config_for_test(&home);
|
||||
let providers = built_in_model_providers();
|
||||
config.model_provider = providers
|
||||
.get("openai")
|
||||
.expect("builtin provider")
|
||||
.clone();
|
||||
|
||||
let cm = ConversationManager::with_auth(CodexAuth::from_api_key("test"));
|
||||
let conv = cm
|
||||
.new_conversation(config)
|
||||
.await
|
||||
.expect("spawn conversation")
|
||||
.conversation;
|
||||
|
||||
conv
|
||||
.submit(codex_core::protocol::Op::UserInput {
|
||||
items: vec![codex_core::protocol::InputItem::Text {
|
||||
text: "go".into(),
|
||||
}],
|
||||
})
|
||||
.await
|
||||
.expect("submit");
|
||||
|
||||
let mut begins = 0usize;
|
||||
let mut ends = 0usize;
|
||||
let mut seen_first_end = false;
|
||||
|
||||
use tokio::time::{sleep, Duration as TokioDuration};
|
||||
let deadline = Instant::now() + TokioDuration::from_secs(5);
|
||||
|
||||
while Instant::now() < deadline {
|
||||
let ev = conv.next_event().await.expect("event");
|
||||
match ev.msg {
|
||||
EventMsg::ExecCommandBegin(_) => {
|
||||
begins += 1;
|
||||
}
|
||||
EventMsg::ExecCommandEnd(_) => {
|
||||
if !seen_first_end {
|
||||
assert_eq!(begins, 2, "expected both begins before first end");
|
||||
seen_first_end = true;
|
||||
}
|
||||
ends += 1;
|
||||
}
|
||||
EventMsg::TaskComplete(_) => break,
|
||||
_ => {}
|
||||
}
|
||||
sleep(TokioDuration::from_millis(5)).await;
|
||||
}
|
||||
|
||||
assert_eq!(begins, 2, "expected two parallel exec begins");
|
||||
assert_eq!(ends, 2, "expected two exec completions");
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
source: tui/src/chatwidget/tests.rs
|
||||
expression: s
|
||||
---
|
||||
>_
|
||||
✓ ⌨️ echo A
|
||||
⠋ ⌨️ echo B
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
source: tui/src/chatwidget/tests.rs
|
||||
expression: s
|
||||
---
|
||||
>_
|
||||
⠋ ⌨️ echo one
|
||||
⠋ ⌨️ echo two
|
||||
@@ -214,6 +214,106 @@ fn lines_to_single_string(lines: &[ratatui::text::Line<'static>]) -> String {
|
||||
s
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn active_exec_segments_render_for_parallel_begins_snapshot() {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual();
|
||||
|
||||
// Begin first command
|
||||
chat.handle_codex_event(Event {
|
||||
id: "call-1".into(),
|
||||
msg: EventMsg::ExecCommandBegin(ExecCommandBeginEvent {
|
||||
call_id: "call-1".into(),
|
||||
command: vec!["bash".into(), "-lc".into(), "echo one".into()],
|
||||
cwd: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
|
||||
parsed_cmd: vec![
|
||||
codex_core::parse_command::ParsedCommand::Unknown {
|
||||
cmd: "echo one".into(),
|
||||
}
|
||||
.into(),
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
// Begin a second command (parallel segment)
|
||||
chat.handle_codex_event(Event {
|
||||
id: "call-2".into(),
|
||||
msg: EventMsg::ExecCommandBegin(ExecCommandBeginEvent {
|
||||
call_id: "call-2".into(),
|
||||
command: vec!["bash".into(), "-lc".into(), "echo two".into()],
|
||||
cwd: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
|
||||
parsed_cmd: vec![
|
||||
codex_core::parse_command::ParsedCommand::Unknown {
|
||||
cmd: "echo two".into(),
|
||||
}
|
||||
.into(),
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
// Snapshot the active exec cell (shows spinners for both segments)
|
||||
let cell = chat.active_exec_cell.as_ref().expect("active exec cell");
|
||||
let s = lines_to_single_string(&cell.display_lines());
|
||||
assert_snapshot!(s);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn active_exec_segments_mark_completion_in_place_snapshot() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual();
|
||||
|
||||
// Begin two commands
|
||||
chat.handle_codex_event(Event {
|
||||
id: "call-a".into(),
|
||||
msg: EventMsg::ExecCommandBegin(ExecCommandBeginEvent {
|
||||
call_id: "call-a".into(),
|
||||
command: vec!["bash".into(), "-lc".into(), "echo A".into()],
|
||||
cwd: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
|
||||
parsed_cmd: vec![
|
||||
codex_core::parse_command::ParsedCommand::Unknown {
|
||||
cmd: "echo A".into(),
|
||||
}
|
||||
.into(),
|
||||
],
|
||||
}),
|
||||
});
|
||||
chat.handle_codex_event(Event {
|
||||
id: "call-b".into(),
|
||||
msg: EventMsg::ExecCommandBegin(ExecCommandBeginEvent {
|
||||
call_id: "call-b".into(),
|
||||
command: vec!["bash".into(), "-lc".into(), "echo B".into()],
|
||||
cwd: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
|
||||
parsed_cmd: vec![
|
||||
codex_core::parse_command::ParsedCommand::Unknown {
|
||||
cmd: "echo B".into(),
|
||||
}
|
||||
.into(),
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
// Complete first command successfully; second still running
|
||||
chat.handle_codex_event(Event {
|
||||
id: "call-a".into(),
|
||||
msg: EventMsg::ExecCommandEnd(ExecCommandEndEvent {
|
||||
call_id: "call-a".into(),
|
||||
stdout: "A".into(),
|
||||
stderr: String::new(),
|
||||
aggregated_output: "A".into(),
|
||||
exit_code: 0,
|
||||
duration: std::time::Duration::from_millis(5),
|
||||
formatted_output: "A".into(),
|
||||
}),
|
||||
});
|
||||
|
||||
// Drain any history insertions (none expected yet) and snapshot active cell
|
||||
let _ = drain_insert_history(&mut rx);
|
||||
let cell = chat
|
||||
.active_exec_cell
|
||||
.as_ref()
|
||||
.expect("active exec cell after first end");
|
||||
let s = lines_to_single_string(&cell.display_lines());
|
||||
assert_snapshot!(s);
|
||||
}
|
||||
|
||||
fn open_fixture(name: &str) -> std::fs::File {
|
||||
// 1) Prefer fixtures within this crate
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user