From 18a943582ba2dbdfadba9e9223def8a58e79e40d Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Thu, 5 Mar 2026 02:38:33 -0800 Subject: [PATCH] codex: fix notify helper resolution in app-server tests (#13534) --- codex-rs/app-server/BUILD.bazel | 1 - codex-rs/app-server/Cargo.toml | 4 ++ codex-rs/app-server/src/bin/notify_capture.rs | 61 +++++++++++++++++++ codex-rs/app-server/tests/common/Cargo.toml | 4 -- .../tests/common/src/bin/notify_capture.rs | 22 ------- .../app-server/tests/suite/v2/initialize.rs | 31 ++++++++-- 6 files changed, 91 insertions(+), 32 deletions(-) create mode 100644 codex-rs/app-server/src/bin/notify_capture.rs delete mode 100644 codex-rs/app-server/tests/common/src/bin/notify_capture.rs diff --git a/codex-rs/app-server/BUILD.bazel b/codex-rs/app-server/BUILD.bazel index 8910019449..5f1ba790d5 100644 --- a/codex-rs/app-server/BUILD.bazel +++ b/codex-rs/app-server/BUILD.bazel @@ -3,6 +3,5 @@ load("//:defs.bzl", "codex_rust_crate") codex_rust_crate( name = "app-server", crate_name = "codex_app_server", - extra_binaries = ["//codex-rs/app-server/tests/common:codex-app-server-test-notify-capture"], test_tags = ["no-sandbox"], ) diff --git a/codex-rs/app-server/Cargo.toml b/codex-rs/app-server/Cargo.toml index ca7f7bffb7..c0ca50f697 100644 --- a/codex-rs/app-server/Cargo.toml +++ b/codex-rs/app-server/Cargo.toml @@ -8,6 +8,10 @@ license.workspace = true name = "codex-app-server" path = "src/main.rs" +[[bin]] +name = "codex-app-server-test-notify-capture" +path = "src/bin/notify_capture.rs" + [lib] name = "codex_app_server" path = "src/lib.rs" diff --git a/codex-rs/app-server/src/bin/notify_capture.rs b/codex-rs/app-server/src/bin/notify_capture.rs new file mode 100644 index 0000000000..3ecffc31f7 --- /dev/null +++ b/codex-rs/app-server/src/bin/notify_capture.rs @@ -0,0 +1,61 @@ +use std::env; +use std::fs::File; +use std::fs::OpenOptions; +use std::io::Write; +use std::path::Path; +use std::path::PathBuf; + +use anyhow::Context; +use anyhow::Result; +use anyhow::anyhow; +use anyhow::bail; + +fn append_log(log_path: &Path, message: &str) { + if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(log_path) { + let _ = writeln!(file, "{message}"); + let _ = file.sync_all(); + } +} + +fn main() -> Result<()> { + let mut args = env::args_os(); + let _program = args.next(); + let output_path = PathBuf::from( + args.next() + .ok_or_else(|| anyhow!("expected output path as first argument"))?, + ); + let log_path = PathBuf::from( + args.next() + .ok_or_else(|| anyhow!("expected log path as second argument"))?, + ); + let payload = args + .next() + .ok_or_else(|| anyhow!("expected payload as final argument"))?; + + append_log( + &log_path, + &format!( + "start cwd={} output={}", + env::current_dir()?.display(), + output_path.display() + ), + ); + + if args.next().is_some() { + append_log(&log_path, "unexpected extra argument"); + bail!("expected payload as final argument"); + } + + let payload = payload.to_string_lossy(); + append_log(&log_path, &format!("payload-bytes={}", payload.len())); + + let mut file = File::create(&output_path) + .with_context(|| format!("failed to create {}", output_path.display()))?; + file.write_all(payload.as_bytes()) + .with_context(|| format!("failed to write {}", output_path.display()))?; + file.sync_all() + .with_context(|| format!("failed to sync {}", output_path.display()))?; + + append_log(&log_path, &format!("wrote {}", output_path.display())); + Ok(()) +} diff --git a/codex-rs/app-server/tests/common/Cargo.toml b/codex-rs/app-server/tests/common/Cargo.toml index ace6d2b467..de58509f0d 100644 --- a/codex-rs/app-server/tests/common/Cargo.toml +++ b/codex-rs/app-server/tests/common/Cargo.toml @@ -7,10 +7,6 @@ license.workspace = true [lib] path = "lib.rs" -[[bin]] -name = "codex-app-server-test-notify-capture" -path = "src/bin/notify_capture.rs" - [dependencies] anyhow = { workspace = true } base64 = { workspace = true } diff --git a/codex-rs/app-server/tests/common/src/bin/notify_capture.rs b/codex-rs/app-server/tests/common/src/bin/notify_capture.rs deleted file mode 100644 index 846e37a300..0000000000 --- a/codex-rs/app-server/tests/common/src/bin/notify_capture.rs +++ /dev/null @@ -1,22 +0,0 @@ -use std::env; -use std::fs::File; -use std::io::Write; -use std::path::PathBuf; - -fn main() -> anyhow::Result<()> { - let mut args = env::args_os(); - let _program = args.next(); - let output_path = PathBuf::from( - args.next() - .ok_or_else(|| anyhow::anyhow!("expected output path as first argument"))?, - ); - let payload = args - .next() - .ok_or_else(|| anyhow::anyhow!("expected payload as final argument"))?; - - let mut file = File::create(&output_path)?; - file.write_all(payload.to_string_lossy().as_bytes())?; - file.sync_all()?; - - Ok(()) -} diff --git a/codex-rs/app-server/tests/suite/v2/initialize.rs b/codex-rs/app-server/tests/suite/v2/initialize.rs index 7e4ab71867..68dd65146d 100644 --- a/codex-rs/app-server/tests/suite/v2/initialize.rs +++ b/codex-rs/app-server/tests/suite/v2/initialize.rs @@ -194,6 +194,7 @@ async fn turn_start_notify_payload_includes_initialize_client_name() -> Result<( let server = create_mock_responses_server_sequence_unchecked(responses).await; let codex_home = TempDir::new()?; let notify_file = codex_home.path().join("notify.json"); + let notify_log = codex_home.path().join("notify.log"); let notify_capture = cargo_bin("codex-app-server-test-notify-capture")?; let notify_capture = notify_capture .to_str() @@ -201,14 +202,18 @@ async fn turn_start_notify_payload_includes_initialize_client_name() -> Result<( let notify_file_str = notify_file .to_str() .expect("notify file path should be valid UTF-8"); + let notify_log_str = notify_log + .to_str() + .expect("notify log path should be valid UTF-8"); create_config_toml_with_extra( codex_home.path(), &server.uri(), "never", &format!( - "notify = [{}, {}]", + "notify = [{}, {}, {}]", toml_basic_string(notify_capture), - toml_basic_string(notify_file_str) + toml_basic_string(notify_file_str), + toml_basic_string(notify_log_str) ), )?; @@ -256,27 +261,43 @@ async fn turn_start_notify_payload_includes_initialize_client_name() -> Result<( ) .await??; - let payload = wait_for_json_file(¬ify_file).await?; + let payload = wait_for_json_file(¬ify_file, ¬ify_log).await?; assert_eq!(payload["client"], "xcode"); Ok(()) } -async fn wait_for_json_file(path: &Path) -> Result { +async fn wait_for_json_file(path: &Path, log_path: &Path) -> Result { let deadline = Instant::now() + Duration::from_secs(5); + let mut last_contents = None; loop { match tokio::fs::read_to_string(path).await { Ok(contents) => { if let Ok(payload) = serde_json::from_str(&contents) { return Ok(payload); } + last_contents = Some(contents); } Err(err) if err.kind() == std::io::ErrorKind::NotFound => {} Err(err) => return Err(err.into()), } if Instant::now() >= deadline { - anyhow::bail!("timed out waiting for valid JSON in {}", path.display()); + let helper_log = match tokio::fs::read_to_string(log_path).await { + Ok(contents) => contents, + Err(err) if err.kind() == std::io::ErrorKind::NotFound => { + "".to_string() + } + Err(err) => format!(""), + }; + let last_contents = + last_contents.unwrap_or_else(|| "".to_string()); + anyhow::bail!( + "timed out waiting for valid JSON in {}. helper log: {} last contents: {}", + path.display(), + helper_log.trim_end(), + last_contents.trim_end() + ); } sleep(Duration::from_millis(25)).await;