use std::path::{Path, PathBuf};
use std::time::Duration;
use crate::{Codex, CodexBuilder};
pub(crate) struct PidFile {
path: PathBuf,
}
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')
}