diff --git a/codex-rs/core/src/exec.rs b/codex-rs/core/src/exec.rs index c65afeb440..f45ecdce75 100644 --- a/codex-rs/core/src/exec.rs +++ b/codex-rs/core/src/exec.rs @@ -818,6 +818,15 @@ mod tests { #[cfg(unix)] #[tokio::test] async fn kill_child_process_group_kills_grandchildren_on_timeout() -> Result<()> { + // On Linux/macOS, /bin/bash is typically present; on FreeBSD/OpenBSD, + // prefer /bin/sh to avoid NotFound errors. + #[cfg(any(target_os = "freebsd", target_os = "openbsd"))] + let command = vec![ + "/bin/sh".to_string(), + "-c".to_string(), + "sleep 60 & echo $!; sleep 60".to_string(), + ]; + #[cfg(all(unix, not(any(target_os = "freebsd", target_os = "openbsd"))))] let command = vec![ "/bin/bash".to_string(), "-c".to_string(), diff --git a/codex-rs/core/src/shell.rs b/codex-rs/core/src/shell.rs index a275b2e399..ac115facb6 100644 --- a/codex-rs/core/src/shell.rs +++ b/codex-rs/core/src/shell.rs @@ -358,7 +358,8 @@ mod tests { assert!( shell_path == PathBuf::from("/bin/bash") - || shell_path == PathBuf::from("/usr/bin/bash"), + || shell_path == PathBuf::from("/usr/bin/bash") + || shell_path == PathBuf::from("/usr/local/bin/bash"), "shell path: {shell_path:?}", ); } diff --git a/codex-rs/core/tests/suite/model_tools.rs b/codex-rs/core/tests/suite/model_tools.rs index b44f53a7c3..e807ce7db0 100644 --- a/codex-rs/core/tests/suite/model_tools.rs +++ b/codex-rs/core/tests/suite/model_tools.rs @@ -1,21 +1,10 @@ #![allow(clippy::unwrap_used)] -use codex_core::CodexAuth; -use codex_core::ConversationManager; -use codex_core::ModelProviderInfo; -use codex_core::built_in_model_providers; -use codex_core::features::Feature; -use codex_core::model_family::find_family_for_model; -use codex_core::protocol::EventMsg; -use codex_core::protocol::Op; -use codex_protocol::user_input::UserInput; -use core_test_support::load_default_config_for_test; use core_test_support::load_sse_fixture_with_id; use core_test_support::responses; +use core_test_support::responses::start_mock_server; use core_test_support::skip_if_no_network; -use core_test_support::wait_for_event; -use tempfile::TempDir; -use wiremock::MockServer; +use core_test_support::test_codex::test_codex; fn sse_completed(id: &str) -> String { load_sse_fixture_with_id("tests/fixtures/completed_template.json", id) @@ -39,46 +28,17 @@ fn tool_identifiers(body: &serde_json::Value) -> Vec { #[allow(clippy::expect_used)] async fn collect_tool_identifiers_for_model(model: &str) -> Vec { - let server = MockServer::start().await; - + let server = start_mock_server().await; let sse = sse_completed(model); let resp_mock = responses::mount_sse_once(&server, sse).await; - let model_provider = ModelProviderInfo { - base_url: Some(format!("{}/v1", server.uri())), - ..built_in_model_providers()["openai"].clone() - }; - - let cwd = TempDir::new().unwrap(); - let codex_home = TempDir::new().unwrap(); - let mut config = load_default_config_for_test(&codex_home); - config.cwd = cwd.path().to_path_buf(); - config.model_provider = model_provider; - config.model = model.to_string(); - config.model_family = - find_family_for_model(model).unwrap_or_else(|| panic!("unknown model family for {model}")); - config.features.disable(Feature::ApplyPatchFreeform); - config.features.disable(Feature::ViewImageTool); - config.features.disable(Feature::WebSearchRequest); - config.features.disable(Feature::UnifiedExec); - - let conversation_manager = - ConversationManager::with_auth(CodexAuth::from_api_key("Test API Key")); - let codex = conversation_manager - .new_conversation(config) + let mut builder = test_codex().with_model(model); + let test = builder + .build(&server) .await - .expect("create new conversation") - .conversation; + .expect("create test Codex conversation"); - codex - .submit(Op::UserInput { - items: vec![UserInput::Text { - text: "hello tools".into(), - }], - }) - .await - .unwrap(); - wait_for_event(&codex, |ev| matches!(ev, EventMsg::TaskComplete(_))).await; + test.submit_turn("hello tools").await.expect("submit turn"); let body = resp_mock.single_request().body_json(); tool_identifiers(&body) @@ -97,7 +57,8 @@ async fn model_selects_expected_tools() { "list_mcp_resources".to_string(), "list_mcp_resource_templates".to_string(), "read_mcp_resource".to_string(), - "update_plan".to_string() + "update_plan".to_string(), + "view_image".to_string() ], "codex-mini-latest should expose the local shell tool", ); @@ -111,7 +72,8 @@ async fn model_selects_expected_tools() { "list_mcp_resource_templates".to_string(), "read_mcp_resource".to_string(), "update_plan".to_string(), - "apply_patch".to_string() + "apply_patch".to_string(), + "view_image".to_string() ], "gpt-5-codex should expose the apply_patch tool", ); @@ -125,7 +87,8 @@ async fn model_selects_expected_tools() { "list_mcp_resource_templates".to_string(), "read_mcp_resource".to_string(), "update_plan".to_string(), - "apply_patch".to_string() + "apply_patch".to_string(), + "view_image".to_string() ], "gpt-5.1-codex should expose the apply_patch tool", ); @@ -139,6 +102,7 @@ async fn model_selects_expected_tools() { "list_mcp_resource_templates".to_string(), "read_mcp_resource".to_string(), "update_plan".to_string(), + "view_image".to_string() ], "gpt-5 should expose the apply_patch tool", ); @@ -152,7 +116,8 @@ async fn model_selects_expected_tools() { "list_mcp_resource_templates".to_string(), "read_mcp_resource".to_string(), "update_plan".to_string(), - "apply_patch".to_string() + "apply_patch".to_string(), + "view_image".to_string() ], "gpt-5.1 should expose the apply_patch tool", ); diff --git a/codex-rs/core/tests/suite/shell_serialization.rs b/codex-rs/core/tests/suite/shell_serialization.rs index 4db2847678..9c49e95f57 100644 --- a/codex-rs/core/tests/suite/shell_serialization.rs +++ b/codex-rs/core/tests/suite/shell_serialization.rs @@ -366,7 +366,7 @@ async fn shell_output_for_freeform_tool_records_duration( let test = builder.build(&server).await?; let call_id = "shell-structured"; - let responses = shell_responses(call_id, vec!["/bin/bash", "-c", "sleep 1"], output_type)?; + let responses = shell_responses(call_id, vec!["/bin/sh", "-c", "sleep 1"], output_type)?; let mock = mount_sse_sequence(&server, responses).await; test.submit_turn_with_policy(