Move the exec-server test fixture into shared test support (#52674)

## What changed

Move `ExecServerProcess` into `core_test_support::exec_server` and update the sandbox, plugin, access, and turn-input tests to use it. Preserve the fixture's startup timeout, temporary `CODEX_HOME`, WebSocket URL discovery, and child-process cleanup.

GitOrigin-RevId: 152de3a83f4ad39e2c1223e15438608dba452850
This commit is contained in:
Adam Perry @ OpenAI
2026-10-09 18:58:53 +00:00
committed by copyberry
parent 04ad786998
commit cb6f6141f8
6 changed files with 79 additions and 68 deletions
+73
View File
@@ -0,0 +1,73 @@
//! Shared native exec-server process fixture; dropping it stops the child process.
use anyhow::Context;
use anyhow::Result;
use anyhow::bail;
use std::process::Stdio;
use std::time::Duration;
use tempfile::TempDir;
use tokio::io::AsyncBufReadExt;
use tokio::io::BufReader;
use tokio::process::Child;
use tokio::process::ChildStdout;
use tokio::process::Command;
use tokio::time::Instant;
use tokio::time::timeout;
const EXEC_SERVER_START_TIMEOUT: Duration = Duration::from_secs(30);
pub struct ExecServerProcess {
_codex_home: TempDir,
child: Child,
_stdout: BufReader<ChildStdout>,
pub websocket_url: String,
}
impl ExecServerProcess {
pub async fn start() -> Result<Self> {
let codex_home = TempDir::new()?;
let mut child = Command::new(codex_utils_cargo_bin::cargo_bin("codex")?)
.args(["exec-server", "--listen", "ws://127.0.0.1:0"])
.env("CODEX_HOME", codex_home.path())
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.kill_on_drop(true)
.spawn()?;
let stdout = child
.stdout
.take()
.context("exec-server stdout should be piped")?;
let mut stdout = BufReader::new(stdout);
let deadline = Instant::now() + EXEC_SERVER_START_TIMEOUT;
let websocket_url = loop {
let remaining = deadline
.checked_duration_since(Instant::now())
.context("timed out waiting for exec-server listen URL")?;
let mut line = String::new();
let bytes_read = timeout(remaining, stdout.read_line(&mut line))
.await
.context("timed out reading exec-server listen URL")??;
if bytes_read == 0 {
bail!("exec-server exited before printing its listen URL");
}
let line = line.trim();
if line.starts_with("ws://") {
break line.to_string();
}
};
Ok(Self {
_codex_home: codex_home,
child,
_stdout: stdout,
websocket_url,
})
}
}
impl Drop for ExecServerProcess {
fn drop(&mut self) {
let _ = self.child.start_kill();
}
}
+1
View File
@@ -26,6 +26,7 @@ use std::path::PathBuf;
pub mod apps_test_server;
pub mod context_snapshot;
pub mod exec_server;
pub mod hooks;
pub mod process;
pub mod responses;
+1 -1
View File
@@ -59,7 +59,7 @@ async fn daybreak_metadata_follows_the_actual_plugin_call(case: AccessCase) -> R
skip_if_no_network!(Ok(()));
#[cfg(unix)]
let remote = if matches!(case, AccessCase::RemoteStdio) {
Some(super::multi_exec_server_sandbox::ExecServerProcess::start().await?)
Some(core_test_support::exec_server::ExecServerProcess::start().await?)
} else {
None
};
@@ -1,11 +1,9 @@
use codex_core::TurnInputRequest;
use codex_protocol::protocol::TurnEnvironmentRequests;
use std::process::Stdio;
use std::time::Duration;
use anyhow::Context;
use anyhow::Result;
use anyhow::bail;
use codex_protocol::models::PermissionProfile;
use codex_protocol::permissions::NetworkSandboxPolicy;
use codex_protocol::protocol::AskForApproval;
@@ -15,6 +13,7 @@ use codex_protocol::protocol::ThreadSettingsOverrides;
use codex_protocol::protocol::TurnEnvironmentSelection;
use codex_protocol::user_input::UserInput;
use codex_utils_path_uri::PathUri;
use core_test_support::exec_server::ExecServerProcess;
use core_test_support::responses::ev_assistant_message;
use core_test_support::responses::ev_completed;
use core_test_support::responses::ev_function_call;
@@ -28,12 +27,6 @@ use core_test_support::wait_for_event_with_timeout;
use pretty_assertions::assert_eq;
use serde_json::json;
use tempfile::TempDir;
use tokio::io::AsyncBufReadExt;
use tokio::io::BufReader;
use tokio::process::Child;
use tokio::process::ChildStdout;
use tokio::process::Command;
use tokio::time::Instant;
use tokio::time::timeout;
const FIRST_ENVIRONMENT_ID: &str = "first";
@@ -43,62 +36,6 @@ const SECOND_CALL_ID: &str = "write-from-second";
const EXEC_SERVER_START_TIMEOUT: Duration = Duration::from_secs(30);
const TURN_COMPLETE_TIMEOUT: Duration = Duration::from_secs(30);
pub(super) struct ExecServerProcess {
_codex_home: TempDir,
child: Child,
_stdout: BufReader<ChildStdout>,
pub(super) websocket_url: String,
}
impl ExecServerProcess {
pub(super) async fn start() -> Result<Self> {
let codex_home = TempDir::new()?;
let mut child = Command::new(codex_utils_cargo_bin::cargo_bin("codex")?)
.args(["exec-server", "--listen", "ws://127.0.0.1:0"])
.env("CODEX_HOME", codex_home.path())
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.kill_on_drop(true)
.spawn()?;
let stdout = child
.stdout
.take()
.context("exec-server stdout should be piped")?;
let mut stdout = BufReader::new(stdout);
let deadline = Instant::now() + EXEC_SERVER_START_TIMEOUT;
let websocket_url = loop {
let remaining = deadline
.checked_duration_since(Instant::now())
.context("timed out waiting for exec-server listen URL")?;
let mut line = String::new();
let bytes_read = timeout(remaining, stdout.read_line(&mut line))
.await
.context("timed out reading exec-server listen URL")??;
if bytes_read == 0 {
bail!("exec-server exited before printing its listen URL");
}
let line = line.trim();
if line.starts_with("ws://") {
break line.to_string();
}
};
Ok(Self {
_codex_home: codex_home,
child,
_stdout: stdout,
websocket_url,
})
}
}
impl Drop for ExecServerProcess {
fn drop(&mut self) {
let _ = self.child.start_kill();
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn two_exec_servers_isolate_workspace_write_roots() -> Result<()> {
let first_exec_server = ExecServerProcess::start().await?;
+2 -2
View File
@@ -674,7 +674,7 @@ printf 'STDIN_OK\n'
});
// Exercise both native and executor-managed sandboxes with the same plugin fixture.
let _executor = if environment_id == codex_exec_server::REMOTE_ENVIRONMENT_ID {
let executor = super::multi_exec_server_sandbox::ExecServerProcess::start().await?;
let executor = core_test_support::exec_server::ExecServerProcess::start().await?;
builder = builder.with_exec_server_url(executor.websocket_url.clone());
Some(executor)
} else {
@@ -732,7 +732,7 @@ async fn remote_plugin_measurements_require_the_frontend_version() -> Result<()>
skip_if_no_network!(Ok(()));
// This fixture needs a real remote transport with separately controlled frontend
// and executor caches, rather than the runner's automatic executor selection.
let executor = super::multi_exec_server_sandbox::ExecServerProcess::start().await?;
let executor = core_test_support::exec_server::ExecServerProcess::start().await?;
let server = start_mock_server().await;
let home = Arc::new(TempDir::new()?);
let frontend_script = write_remote_plugin_script_and_config(home.as_ref());
@@ -1465,7 +1465,7 @@ async fn sampling_is_ready_for_daemon_recovery(
.await;
#[cfg(unix)]
let remote = if executor == "remote" {
Some(super::multi_exec_server_sandbox::ExecServerProcess::start().await?)
Some(core_test_support::exec_server::ExecServerProcess::start().await?)
} else {
None
};