fix: integration test for #9011

This commit is contained in:
Michael Bolin
2026-01-13 13:29:25 -08:00
parent b21b75693a
commit dd01533396
7 changed files with 233 additions and 0 deletions

3
codex-rs/Cargo.lock generated
View File

@@ -1746,6 +1746,7 @@ dependencies = [
"codex-app-server-protocol",
"codex-arg0",
"codex-backend-client",
"codex-cli",
"codex-common",
"codex-core",
"codex-feedback",
@@ -1753,6 +1754,8 @@ dependencies = [
"codex-login",
"codex-protocol",
"codex-utils-absolute-path",
"codex-utils-cargo-bin",
"codex-utils-pty",
"codex-windows-sandbox",
"color-eyre",
"crossterm",

View File

@@ -71,6 +71,7 @@ codex-arg0 = { path = "arg0" }
codex-async-utils = { path = "async-utils" }
codex-backend-client = { path = "backend-client" }
codex-chatgpt = { path = "chatgpt" }
codex-cli = { path = "cli"}
codex-client = { path = "codex-client" }
codex-common = { path = "common" }
codex-core = { path = "core" }

View File

@@ -113,7 +113,10 @@ arboard = { workspace = true }
[dev-dependencies]
codex-cli = { workspace = true }
codex-core = { workspace = true, features = ["test-support"] }
codex-utils-cargo-bin = { workspace = true }
codex-utils-pty = { workspace = true }
assert_matches = { workspace = true }
chrono = { workspace = true, features = ["serde"] }
insta = { workspace = true }

View File

@@ -1,4 +1,5 @@
// Aggregates all former standalone integration tests as modules.
mod no_panic_on_startup;
mod status_indicator;
mod vt100_history;
mod vt100_live_commit;

View File

@@ -0,0 +1,112 @@
use std::collections::HashMap;
use std::path::Path;
use std::time::Duration;
use tokio::select;
use tokio::time::timeout;
/// Regression test for https://github.com/openai/codex/issues/8803.
#[tokio::test]
async fn malformed_rules_should_not_panic() -> anyhow::Result<()> {
if cfg!(windows) && !codex_utils_pty::conpty_supported() {
return Ok(());
}
let tmp = tempfile::tempdir()?;
let codex_home = tmp.path();
std::fs::write(
codex_home.join("rules"),
"rules should be a directory not a file",
)?;
// TODO(mbolin): Figure out why using a temp dir as the cwd causes this test
// to hang.
let cwd = std::env::current_dir()?;
let config_contents = format!(
r#"
model_provider = "ollama"
[projects]
"{cwd}" = {{ trust_level = "trusted" }}
"#,
cwd = cwd.display()
);
std::fs::write(codex_home.join("config.toml"), config_contents)?;
let CodexCliOutput { exit_code, output } = run_codex_cli(codex_home, cwd).await?;
assert_eq!(1, exit_code, "Codex CLI should exit nonzero.");
assert!(
output.contains("ERROR: Failed to initialize codex:"),
"expected startup error in output, got: {output}"
);
assert!(
output.contains("failed to read execpolicy files"),
"expected execpolicy read error in output, got: {output}"
);
Ok(())
}
struct CodexCliOutput {
exit_code: i32,
output: String,
}
async fn run_codex_cli(
codex_home: impl AsRef<Path>,
cwd: impl AsRef<Path>,
) -> anyhow::Result<CodexCliOutput> {
let codex_cli = codex_utils_cargo_bin::cargo_bin("codex")?;
let mut env = HashMap::new();
env.insert(
"CODEX_HOME".to_string(),
codex_home.as_ref().display().to_string(),
);
let args = vec!["-c".to_string(), "analytics_enabled=false".to_string()];
let spawned = codex_utils_pty::spawn_pty_process(
codex_cli.to_string_lossy().as_ref(),
&args,
cwd.as_ref(),
&env,
&None,
)
.await?;
let mut output = Vec::new();
let mut output_rx = spawned.output_rx;
let mut exit_rx = spawned.exit_rx;
let writer_tx = spawned.session.writer_sender();
let exit_code_result = timeout(Duration::from_secs(10), async {
loop {
select! {
result = output_rx.recv() => match result {
Ok(chunk) => {
if chunk.windows(4).any(|window| window == b"\x1b[6n") {
let _ = writer_tx.send(b"\x1b[1;1R".to_vec()).await;
}
output.extend_from_slice(&chunk);
}
Err(tokio::sync::broadcast::error::RecvError::Closed) => break exit_rx.await,
Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => {}
},
result = &mut exit_rx => break result,
}
}
})
.await;
let exit_code = match exit_code_result {
Ok(Ok(code)) => code,
Ok(Err(err)) => return Err(err.into()),
Err(_) => {
spawned.session.terminate();
anyhow::bail!("timed out waiting for codex CLI to exit");
}
};
while let Ok(chunk) = output_rx.try_recv() {
output.extend_from_slice(&chunk);
}
let output = String::from_utf8_lossy(&output);
Ok(CodexCliOutput {
exit_code,
output: output.to_string(),
})
}

View File

@@ -1,4 +1,5 @@
// Aggregates all former standalone integration tests as modules.
mod no_panic_on_startup;
mod status_indicator;
mod vt100_history;
mod vt100_live_commit;

View File

@@ -0,0 +1,112 @@
use std::collections::HashMap;
use std::path::Path;
use std::time::Duration;
use tokio::select;
use tokio::time::timeout;
/// Regression test for https://github.com/openai/codex/issues/8803.
#[tokio::test]
async fn malformed_rules_should_not_panic() -> anyhow::Result<()> {
if cfg!(windows) && !codex_utils_pty::conpty_supported() {
return Ok(());
}
let tmp = tempfile::tempdir()?;
let codex_home = tmp.path();
std::fs::write(
codex_home.join("rules"),
"rules should be a directory not a file",
)?;
// TODO(mbolin): Figure out why using a temp dir as the cwd causes this test
// to hang.
let cwd = std::env::current_dir()?;
let config_contents = format!(
r#"
model_provider = "ollama"
[projects]
"{cwd}" = {{ trust_level = "trusted" }}
"#,
cwd = cwd.display()
);
std::fs::write(codex_home.join("config.toml"), config_contents)?;
let CodexCliOutput { exit_code, output } = run_codex_cli(codex_home, cwd).await?;
assert_eq!(1, exit_code, "Codex CLI should exit nonzero.");
assert!(
output.contains("ERROR: Failed to initialize codex:"),
"expected startup error in output, got: {output}"
);
assert!(
output.contains("failed to read execpolicy files"),
"expected execpolicy read error in output, got: {output}"
);
Ok(())
}
struct CodexCliOutput {
exit_code: i32,
output: String,
}
async fn run_codex_cli(
codex_home: impl AsRef<Path>,
cwd: impl AsRef<Path>,
) -> anyhow::Result<CodexCliOutput> {
let codex_cli = codex_utils_cargo_bin::cargo_bin("codex")?;
let mut env = HashMap::new();
env.insert(
"CODEX_HOME".to_string(),
codex_home.as_ref().display().to_string(),
);
let args = vec!["-c".to_string(), "analytics_enabled=false".to_string()];
let spawned = codex_utils_pty::spawn_pty_process(
codex_cli.to_string_lossy().as_ref(),
&args,
cwd.as_ref(),
&env,
&None,
)
.await?;
let mut output = Vec::new();
let mut output_rx = spawned.output_rx;
let mut exit_rx = spawned.exit_rx;
let writer_tx = spawned.session.writer_sender();
let exit_code_result = timeout(Duration::from_secs(10), async {
loop {
select! {
result = output_rx.recv() => match result {
Ok(chunk) => {
if chunk.windows(4).any(|window| window == b"\x1b[6n") {
let _ = writer_tx.send(b"\x1b[1;1R".to_vec()).await;
}
output.extend_from_slice(&chunk);
}
Err(tokio::sync::broadcast::error::RecvError::Closed) => break exit_rx.await,
Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => {}
},
result = &mut exit_rx => break result,
}
}
})
.await;
let exit_code = match exit_code_result {
Ok(Ok(code)) => code,
Ok(Err(err)) => return Err(err.into()),
Err(_) => {
spawned.session.terminate();
anyhow::bail!("timed out waiting for codex CLI to exit");
}
};
while let Ok(chunk) = output_rx.try_recv() {
output.extend_from_slice(&chunk);
}
let output = String::from_utf8_lossy(&output);
Ok(CodexCliOutput {
exit_code,
output: output.to_string(),
})
}