[codex] Apply patches through executor filesystem (#17048)

## Summary
- run apply_patch through the executor filesystem when a remote
environment is present instead of shelling out to the local process
- thread the executor FileSystem into apply_patch interception and keep
existing local behavior for non-remote turns
- make the apply_patch integration harness use the executor filesystem
for setup/assertions
- add remote-aware skips for turn-diff coverage that still reads the
test-runner filesystem

## Why
Remote apply_patch needed to mutate the remote workspace instead of the
local checkout. The tests also needed to seed and assert workspace state
through the same filesystem abstraction so local and remote runs
exercise the same behavior.

## Validation
- `just fmt`
- `git diff --check`
- `cargo check -p core_test_support --tests`
- `cargo test -p codex-core --test all
suite::shell_serialization::apply_patch_custom_tool_call -- --nocapture`
- `cargo test -p codex-core --test all
suite::apply_patch_cli::apply_patch_cli_updates_file_appends_trailing_newline
-- --nocapture`
- remote `cargo test -p codex-core --test all apply_patch_cli --
--nocapture` (229 passed)
This commit is contained in:
pakrym-oai
2026-04-07 16:35:02 -07:00
committed by GitHub
parent 08797193aa
commit 600c3e49e0
5 changed files with 261 additions and 119 deletions

View File

@@ -303,6 +303,10 @@ pub fn sandbox_network_env_var() -> &'static str {
const REMOTE_ENV_ENV_VAR: &str = "CODEX_TEST_REMOTE_ENV";
pub fn remote_env_env_var() -> &'static str {
REMOTE_ENV_ENV_VAR
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct RemoteEnvConfig {
pub container_name: String,
@@ -537,6 +541,30 @@ macro_rules! skip_if_no_network {
}};
}
#[macro_export]
macro_rules! skip_if_remote {
($reason:expr $(,)?) => {{
if ::std::env::var_os($crate::remote_env_env_var()).is_some() {
eprintln!(
"Skipping test under {}: {}",
$crate::remote_env_env_var(),
$reason
);
return;
}
}};
($return_value:expr, $reason:expr $(,)?) => {{
if ::std::env::var_os($crate::remote_env_env_var()).is_some() {
eprintln!(
"Skipping test under {}: {}",
$crate::remote_env_env_var(),
$reason
);
return $return_value;
}
}};
}
#[macro_export]
macro_rules! codex_linux_sandbox_exe_or_skip {
() => {{

View File

@@ -1,4 +1,5 @@
use std::future::Future;
use std::io::ErrorKind;
use std::mem::swap;
use std::path::Path;
use std::path::PathBuf;
@@ -19,6 +20,7 @@ use codex_core::shell::Shell;
use codex_core::shell::get_shell_by_model_provided_path;
use codex_exec_server::CreateDirectoryOptions;
use codex_exec_server::ExecutorFileSystem;
use codex_exec_server::RemoveOptions;
use codex_features::Feature;
use codex_login::CodexAuth;
use codex_model_provider_info::ModelProviderInfo;
@@ -796,6 +798,12 @@ impl TestCodexHarness {
Ok(Self { server, test })
}
pub async fn with_remote_aware_builder(mut builder: TestCodexBuilder) -> Result<Self> {
let server = start_mock_server().await;
let test = builder.build_remote_aware(&server).await?;
Ok(Self { server, test })
}
pub fn server(&self) -> &MockServer {
&self.server
}
@@ -805,11 +813,75 @@ impl TestCodexHarness {
}
pub fn cwd(&self) -> &Path {
self.test.cwd_path()
self.test.config.cwd.as_path()
}
pub fn path(&self, rel: impl AsRef<Path>) -> PathBuf {
self.test.workspace_path(rel)
self.path_abs(rel).into_path_buf()
}
pub fn path_abs(&self, rel: impl AsRef<Path>) -> AbsolutePathBuf {
self.test.config.cwd.join(rel)
}
pub async fn write_file(
&self,
rel: impl AsRef<Path>,
contents: impl AsRef<[u8]>,
) -> Result<()> {
let abs_path = self.path_abs(rel);
if let Some(parent) = abs_path.parent() {
self.test
.fs()
.create_directory(&parent, CreateDirectoryOptions { recursive: true })
.await?;
}
self.test
.fs()
.write_file(&abs_path, contents.as_ref().to_vec())
.await?;
Ok(())
}
pub async fn read_file_text(&self, rel: impl AsRef<Path>) -> Result<String> {
Ok(self.test.fs().read_file_text(&self.path_abs(rel)).await?)
}
pub async fn create_dir_all(&self, rel: impl AsRef<Path>) -> Result<()> {
self.test
.fs()
.create_directory(
&self.path_abs(rel),
CreateDirectoryOptions { recursive: true },
)
.await?;
Ok(())
}
pub async fn path_exists(&self, rel: impl AsRef<Path>) -> Result<bool> {
self.abs_path_exists(&self.path_abs(rel)).await
}
pub async fn remove_abs_path(&self, path: &AbsolutePathBuf) -> Result<()> {
self.test
.fs()
.remove(
path,
RemoveOptions {
recursive: false,
force: true,
},
)
.await?;
Ok(())
}
pub async fn abs_path_exists(&self, path: &AbsolutePathBuf) -> Result<bool> {
match self.test.fs().get_metadata(path).await {
Ok(_) => Ok(true),
Err(err) if err.kind() == ErrorKind::NotFound => Ok(false),
Err(err) => Err(err.into()),
}
}
pub async fn submit(&self, prompt: &str) -> Result<()> {