use std::path::{Path, PathBuf};
use std::time::Duration;
use crate::{Codex, CodexBuilder};
pub(crate) struct PidFile {
path: PathBuf,
}
pub(crate) struct EnvCapture {
path: PathBuf,
}
impl EnvCapture {
pub(crate) fn new(label: &str) -> Self {
let path =
std::env::temp_dir().join(format!("codex-wrapper-{}-{label}.env", std::process::id()));
let _ = std::fs::remove_file(&path);
Self { path }
}
pub(crate) fn read(&self) -> std::collections::BTreeMap<String, String> {
let contents = std::fs::read_to_string(&self.path)
.unwrap_or_else(|e| panic!("failed to read {}: {e}", self.path.display()));
contents
.lines()
.filter_map(|line| {
let (key, value) = line.split_once('=')?;
Some((key.to_string(), value.to_string()))
})
.collect()
}
}
impl Drop for EnvCapture {
fn drop(&mut self) {
let _ = std::fs::remove_file(&self.path);
}
}
pub(crate) fn env_capturing_codex(capture: &EnvCapture) -> CodexBuilder {
let script = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("fake-codex-capture-env.sh");
Codex::builder()
.binary("/bin/bash")
.arg(script.to_str().expect("fixture path is utf-8"))
.env(
"CODEX_WRAPPER_ENV_CAPTURE",
capture.path.to_str().expect("capture path is utf-8"),
)
}
impl PidFile {
pub(crate) fn new(label: &str) -> Self {
let path =
std::env::temp_dir().join(format!("codex-wrapper-{}-{label}.pid", std::process::id()));
let _ = std::fs::remove_file(&path);
Self { path }
}
pub(crate) fn path(&self) -> &Path {
&self.path
}
pub(crate) async fn read_pid(&self) -> u32 {
for _ in 0..100 {
if let Ok(contents) = std::fs::read_to_string(&self.path)
&& let Ok(pid) = contents.trim().parse()
{
return pid;
}
tokio::time::sleep(Duration::from_millis(10)).await;
}
panic!("fake codex never recorded a pid at {}", self.path.display());
}
}
impl Drop for PidFile {
fn drop(&mut self) {
let _ = std::fs::remove_file(&self.path);
}
}
pub(crate) fn blocking_codex(pid_file: &PidFile) -> CodexBuilder {
let script = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("fake-codex-blocks.sh");
Codex::builder()
.binary("/bin/bash")
.arg(script.to_str().expect("fixture path is utf-8"))
.env(
"CODEX_WRAPPER_TEST_PIDFILE",
pid_file.path().to_str().expect("pid file path is utf-8"),
)
}
pub(crate) async fn wait_until_gone(pid: u32) -> bool {
for _ in 0..250 {
if !is_running(pid) {
return true;
}
tokio::time::sleep(Duration::from_millis(20)).await;
}
false
}
pub(crate) fn is_running_for_test(pid: u32) -> bool {
is_running(pid)
}
fn is_running(pid: u32) -> bool {
let output = std::process::Command::new("ps")
.args(["-o", "state=", "-p", &pid.to_string()])
.output()
.expect("ps must be available");
let state = String::from_utf8_lossy(&output.stdout);
let state = state.trim();
!state.is_empty() && !state.starts_with('Z')
}