From 9b5078d3e8480e75771da24e5ce7ba7588cd7011 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Tue, 10 Mar 2026 17:00:49 -0700 Subject: [PATCH] Stabilize pipe process stdin round-trip test (#14013) ## What changed - keep the explicit stdin-close behavior after writing so the child still receives EOF deterministically - on Windows, stop using `python -c` for the round-trip assertion and instead run a native `cmd.exe` pipeline that reads one line from stdin with `set /p` and echoes it back - send ` ` on Windows so the stdin payload matches the platform-native line ending the shell reader expects ## Why this fixes flakiness The failing branch-local flake was not in `spawn_pipe_process` itself. The child exited cleanly, but the Windows ARM runner sometimes produced an empty stdout string when the test used Python as the stdin consumer. That makes the test sensitive to Python startup and stdin-close timing rather than the pipe primitive we actually want to validate. Switching the Windows path to a native `cmd.exe` reader keeps the assertion focused on our pipe behavior: bytes written to stdin should come back on stdout before EOF closes the process. The explicit ` ` write removes line-ending ambiguity on Windows. ## Scope - test-only - no production logic change --- codex-rs/utils/pty/src/tests.rs | 43 ++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/codex-rs/utils/pty/src/tests.rs b/codex-rs/utils/pty/src/tests.rs index c2856a95c..cc4c002a5 100644 --- a/codex-rs/utils/pty/src/tests.rs +++ b/codex-rs/utils/pty/src/tests.rs @@ -288,21 +288,42 @@ async fn pty_python_repl_emits_output_and_exits() -> anyhow::Result<()> { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn pipe_process_round_trips_stdin() -> anyhow::Result<()> { - let Some(python) = find_python() else { - eprintln!("python not found; skipping pipe_process_round_trips_stdin"); - return Ok(()); + let (program, args) = if cfg!(windows) { + let cmd = std::env::var("COMSPEC").unwrap_or_else(|_| "cmd.exe".to_string()); + ( + cmd, + vec![ + "/Q".to_string(), + "/V:ON".to_string(), + "/D".to_string(), + "/C".to_string(), + "set /p line= & echo(!line!".to_string(), + ], + ) + } else { + let Some(python) = find_python() else { + eprintln!("python not found; skipping pipe_process_round_trips_stdin"); + return Ok(()); + }; + ( + python, + vec![ + "-u".to_string(), + "-c".to_string(), + "import sys; print(sys.stdin.readline().strip());".to_string(), + ], + ) }; - - let args = vec![ - "-u".to_string(), - "-c".to_string(), - "import sys; print(sys.stdin.readline().strip());".to_string(), - ]; let env_map: HashMap = std::env::vars().collect(); - let spawned = spawn_pipe_process(&python, &args, Path::new("."), &env_map, &None).await?; + let spawned = spawn_pipe_process(&program, &args, Path::new("."), &env_map, &None).await?; let (session, output_rx, exit_rx) = combine_spawned_output(spawned); let writer = session.writer_sender(); - writer.send(b"roundtrip\n".to_vec()).await?; + let newline = if cfg!(windows) { "\r\n" } else { "\n" }; + writer + .send(format!("roundtrip{newline}").into_bytes()) + .await?; + drop(writer); + session.close_stdin(); let (output, code) = collect_output_until_exit(output_rx, exit_rx, 5_000).await; let text = String::from_utf8_lossy(&output);