Centralize executable fixture creation to avoid Linux ETXTBSY races (#48727)

## Why

Concurrent tests can inherit writable descriptors for executable fixtures, causing `ETXTBSY` when those fixtures launch on Linux.

## What changed

Expose shared `write_executable` and `copy_executable` helpers from `codex_utils_cargo_bin` and adopt them in CLI, exec-server, and MCP tests. On Linux, writes and copies complete in separate processes so sibling test spawns cannot inherit the writable descriptors. Script fixtures receive mode `0o755`, and copied executables retain their source permissions.

Remove the executable-busy retry loop from the program resolver test now that its script uses the shared helper.

GitOrigin-RevId: 61624158190bb1e75c27d9eb9c87a2defc147fb7
This commit is contained in:
jif
2026-09-27 15:25:13 +00:00
committed by copyberry
parent 88235f881d
commit 18344a972d
10 changed files with 79 additions and 109 deletions
+6 -29
View File
@@ -69,6 +69,7 @@ mod tests {
use super::*;
use crate::utils::create_env_for_mcp_server;
use anyhow::Result;
#[cfg(windows)]
use std::fs;
use std::path::Path;
use tempfile::TempDir;
@@ -79,24 +80,10 @@ mod tests {
#[tokio::test]
async fn test_unix_executes_script_without_extension() -> Result<()> {
let env = TestExecutableEnv::new()?;
// Linux can transiently report ETXTBSY while the freshly written test
// script is becoming executable on the backing filesystem.
let mut retries = 0;
let output = loop {
let mut cmd = Command::new(&env.program_name);
cmd.envs(&env.mcp_env);
let output = cmd.output().await;
if !output
.as_ref()
.is_err_and(|err| err.kind() == std::io::ErrorKind::ExecutableFileBusy)
|| retries == 2
{
break output;
}
retries += 1;
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
};
let output = Command::new(&env.program_name)
.envs(&env.mcp_env)
.output()
.await;
assert!(
output.is_ok(),
@@ -214,22 +201,12 @@ mod tests {
#[cfg(unix)]
{
let file = dir.join(Self::TEST_PROGRAM);
fs::write(&file, "#!/bin/sh\nexit 0")?;
Self::set_executable(&file)?;
codex_utils_cargo_bin::write_executable(&file, "#!/bin/sh\nexit 0")?;
}
Ok(())
}
#[cfg(unix)]
fn set_executable(path: &Path) -> Result<()> {
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(path)?.permissions();
perms.set_mode(0o755);
fs::set_permissions(path, perms)?;
Ok(())
}
/// Prepends the given directory to the system's PATH variable.
fn build_path_env_var(dir: &Path) -> OsString {
let mut path = OsString::from(dir.as_os_str());