Add remote env CI matrix and integration test (#14869)

`CODEX_TEST_REMOTE_ENV` will make `test_codex` start the executor
"remotely" (inside a docker container) turning any integration test into
remote test.
This commit is contained in:
pakrym-oai
2026-03-20 08:02:50 -07:00
committed by GitHub
parent e5f4d1fef5
commit ba85a58039
10 changed files with 514 additions and 23 deletions

View File

@@ -2277,14 +2277,9 @@ async fn code_mode_can_call_hidden_dynamic_tools() -> Result<()> {
false,
)
.await?;
let test = TestCodex {
home: base_test.home,
cwd: base_test.cwd,
codex: new_thread.thread,
session_configured: new_thread.session_configured,
config: base_test.config,
thread_manager: base_test.thread_manager,
};
let mut test = base_test;
test.codex = new_thread.thread;
test.session_configured = new_thread.session_configured;
let code = r#"
import { ALL_TOOLS, hidden_dynamic_tool } from "tools.js";

View File

@@ -103,6 +103,7 @@ mod prompt_caching;
mod quota_exceeded;
mod read_file;
mod realtime_conversation;
mod remote_env;
mod remote_models;
mod request_compression;
#[cfg(not(target_os = "windows"))]

View File

@@ -0,0 +1,57 @@
use anyhow::Result;
use codex_exec_server::RemoveOptions;
use codex_utils_absolute_path::AbsolutePathBuf;
use core_test_support::get_remote_test_env;
use core_test_support::test_codex::test_env;
use pretty_assertions::assert_eq;
use std::path::PathBuf;
use std::time::SystemTime;
use std::time::UNIX_EPOCH;
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn remote_test_env_can_connect_and_use_filesystem() -> Result<()> {
let Some(_remote_env) = get_remote_test_env() else {
return Ok(());
};
let test_env = test_env().await?;
let file_system = test_env.environment().get_filesystem();
let file_path = remote_test_file_path();
let file_path_abs = absolute_path(file_path.clone())?;
let payload = b"remote-test-env-ok".to_vec();
file_system
.write_file(&file_path_abs, payload.clone())
.await?;
let actual = file_system.read_file(&file_path_abs).await?;
assert_eq!(actual, payload);
file_system
.remove(
&file_path_abs,
RemoveOptions {
recursive: false,
force: true,
},
)
.await?;
Ok(())
}
fn absolute_path(path: PathBuf) -> Result<AbsolutePathBuf> {
AbsolutePathBuf::try_from(path.clone())
.map_err(|err| anyhow::anyhow!("invalid absolute path {}: {err}", path.display()))
}
fn remote_test_file_path() -> PathBuf {
let nanos = match SystemTime::now().duration_since(UNIX_EPOCH) {
Ok(duration) => duration.as_nanos(),
Err(_) => 0,
};
PathBuf::from(format!(
"/tmp/codex-remote-test-env-{}-{nanos}.txt",
std::process::id()
))
}

View File

@@ -3,6 +3,7 @@
use base64::Engine;
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
use codex_core::CodexAuth;
use codex_exec_server::CreateDirectoryOptions;
use codex_features::Feature;
use codex_protocol::config_types::ReasoningSummary;
use codex_protocol::openai_models::ConfigShellToolType;
@@ -32,12 +33,16 @@ use core_test_support::test_codex::TestCodex;
use core_test_support::test_codex::test_codex;
use core_test_support::wait_for_event;
use core_test_support::wait_for_event_with_timeout;
use image::DynamicImage;
use image::GenericImageView;
use image::ImageBuffer;
use image::Rgba;
use image::load_from_memory;
use pretty_assertions::assert_eq;
use serde_json::Value;
use std::io::Cursor;
use std::path::Path;
use std::path::PathBuf;
use tokio::time::Duration;
use wiremock::BodyPrintLimit;
use wiremock::MockServer;
@@ -73,6 +78,11 @@ fn find_image_message(body: &Value) -> Option<&Value> {
image_messages(body).into_iter().next()
}
fn absolute_path(path: &Path) -> anyhow::Result<codex_utils_absolute_path::AbsolutePathBuf> {
codex_utils_absolute_path::AbsolutePathBuf::try_from(path.to_path_buf())
.map_err(|err| anyhow::anyhow!("invalid absolute path {}: {err}", path.display()))
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn user_turn_with_local_image_attaches_image() -> anyhow::Result<()> {
skip_if_no_network!(Ok(()));
@@ -171,23 +181,37 @@ async fn view_image_tool_attaches_local_image() -> anyhow::Result<()> {
skip_if_no_network!(Ok(()));
let server = start_mock_server().await;
let mut builder = test_codex();
let test = builder.build_remote_aware(&server).await?;
let TestCodex {
codex,
cwd,
session_configured,
config,
..
} = test_codex().build(&server).await?;
} = &test;
let cwd = config.cwd.clone();
let rel_path = PathBuf::from("assets/example.png");
let abs_path = cwd.join(&rel_path);
let abs_path_absolute = absolute_path(&abs_path)?;
let assets_dir = cwd.join("assets");
let file_system = test.fs();
let rel_path = "assets/example.png";
let abs_path = cwd.path().join(rel_path);
if let Some(parent) = abs_path.parent() {
std::fs::create_dir_all(parent)?;
}
let original_width = 2304;
let original_height = 864;
let image = ImageBuffer::from_pixel(original_width, original_height, Rgba([255u8, 0, 0, 255]));
image.save(&abs_path)?;
let mut cursor = Cursor::new(Vec::new());
DynamicImage::ImageRgba8(image).write_to(&mut cursor, image::ImageFormat::Png)?;
file_system
.create_directory(
&absolute_path(&assets_dir)?,
CreateDirectoryOptions { recursive: true },
)
.await?;
file_system
.write_file(&abs_path_absolute, cursor.into_inner())
.await?;
let call_id = "view-image-call";
let arguments = serde_json::json!({ "path": rel_path }).to_string();
@@ -214,7 +238,7 @@ async fn view_image_tool_attaches_local_image() -> anyhow::Result<()> {
text_elements: Vec::new(),
}],
final_output_json_schema: None,
cwd: cwd.path().to_path_buf(),
cwd: cwd.clone(),
approval_policy: AskForApproval::Never,
sandbox_policy: SandboxPolicy::DangerFullAccess,
model: session_model,
@@ -228,7 +252,7 @@ async fn view_image_tool_attaches_local_image() -> anyhow::Result<()> {
let mut tool_event = None;
wait_for_event_with_timeout(
&codex,
codex,
|event| match event {
EventMsg::ViewImageToolCall(_) => {
tool_event = Some(event.clone());