mirror of
https://github.com/openai/codex.git
synced 2026-11-13 21:30:47 +00:00
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:
@@ -0,0 +1,62 @@
|
||||
//! Creates executable test fixtures without exposing writable descriptors to sibling spawns.
|
||||
//! On Linux, writes and copies finish in a separate process before the fixture can be launched.
|
||||
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
#[cfg(target_os = "linux")]
|
||||
use std::process::Command;
|
||||
|
||||
/// Writes a small Unix script fixture and makes it executable with mode 0o755.
|
||||
#[cfg(unix)]
|
||||
pub fn write_executable(path: &Path, script: &str) -> io::Result<()> {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
// A sibling test can fork while a parent-owned file is open for writing.
|
||||
// CLOEXEC does not release that inherited descriptor until the sibling execs.
|
||||
let output = Command::new("/bin/sh")
|
||||
.arg("-c")
|
||||
.arg("printf '%s' \"$1\" > \"$2\"")
|
||||
.arg("write-executable-fixture")
|
||||
.arg(script)
|
||||
.arg(path)
|
||||
.output()?;
|
||||
if !output.status.success() {
|
||||
return Err(io::Error::other(format!(
|
||||
"failed to write executable fixture {}: {}",
|
||||
path.display(),
|
||||
String::from_utf8_lossy(&output.stderr),
|
||||
)));
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
fs::write(path, script)?;
|
||||
fs::set_permissions(path, fs::Permissions::from_mode(/*mode*/ 0o755))
|
||||
}
|
||||
|
||||
/// Copies an executable fixture, preserving the source permissions like [`fs::copy`].
|
||||
pub fn copy_executable(source: &Path, destination: &Path) -> io::Result<()> {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let output = Command::new("/bin/cp")
|
||||
.arg("--")
|
||||
.arg(source)
|
||||
.arg(destination)
|
||||
.output()?;
|
||||
if !output.status.success() {
|
||||
return Err(io::Error::other(format!(
|
||||
"failed to copy executable fixture {} to {}: {}",
|
||||
source.display(),
|
||||
destination.display(),
|
||||
String::from_utf8_lossy(&output.stderr),
|
||||
)));
|
||||
}
|
||||
// Match fs::copy even when the test process has a restrictive umask.
|
||||
fs::set_permissions(destination, fs::metadata(source)?.permissions())?;
|
||||
}
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
fs::copy(source, destination)?;
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user