diff --git a/codex-rs/app-server/tests/common/responses.rs b/codex-rs/app-server/tests/common/responses.rs index d3d1f40cdc..514df8b044 100644 --- a/codex-rs/app-server/tests/common/responses.rs +++ b/codex-rs/app-server/tests/common/responses.rs @@ -96,16 +96,21 @@ pub fn create_apply_patch_sse_response( } pub fn create_exec_command_sse_response(call_id: &str) -> anyhow::Result { - let (cmd, args) = if cfg!(windows) { - ("cmd.exe", vec!["/d", "/c", "echo hi"]) + let cmd = if cfg!(windows) { + // Keep this as a plain string: our exec tool ultimately runs the string + // via a shell anyway, and joining args naively loses quoting. + "cmd.exe /d /c echo hi".to_string() } else { - ("/bin/sh", vec!["-c", "echo hi"]) + // Keep the command simple and shell-native so it's stable under Buck2's + // sandboxing / symlink trees. + "echo hi".to_string() }; - let command = std::iter::once(cmd.to_string()) - .chain(args.into_iter().map(str::to_string)) - .collect::>(); let tool_call_arguments = serde_json::to_string(&json!({ - "cmd": command.join(" "), + "cmd": cmd, + // Force a non-login shell for determinism: under Buck2 test runners, + // login shells can pick up environment/profile differences that make + // this simple command flaky. + "login": false, "yield_time_ms": 500 }))?; let tool_call = json!({ diff --git a/codex-rs/apply-patch/tests/suite/scenarios.rs b/codex-rs/apply-patch/tests/suite/scenarios.rs index b35ed8590d..0e21a7bc0a 100644 --- a/codex-rs/apply-patch/tests/suite/scenarios.rs +++ b/codex-rs/apply-patch/tests/suite/scenarios.rs @@ -8,7 +8,8 @@ use tempfile::tempdir; #[test] fn test_apply_patch_scenarios() -> anyhow::Result<()> { - for scenario in fs::read_dir("tests/fixtures/scenarios")? { + let scenarios_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/scenarios"); + for scenario in fs::read_dir(scenarios_dir)? { let scenario = scenario?; let path = scenario.path(); if path.is_dir() { @@ -81,11 +82,15 @@ fn snapshot_dir_recursive( continue; }; let rel = stripped.to_path_buf(); - let file_type = entry.file_type()?; - if file_type.is_dir() { + + // Under Buck2, files in `__srcs` are often materialized as symlinks. + // Use `metadata()` (follows symlinks) so our fixture snapshots work + // under both Cargo and Buck2. + let metadata = fs::metadata(&path)?; + if metadata.is_dir() { entries.insert(rel.clone(), Entry::Dir); snapshot_dir_recursive(base, &path, entries)?; - } else if file_type.is_file() { + } else if metadata.is_file() { let contents = fs::read(&path)?; entries.insert(rel, Entry::File(contents)); } @@ -97,12 +102,14 @@ fn copy_dir_recursive(src: &Path, dst: &Path) -> anyhow::Result<()> { for entry in fs::read_dir(src)? { let entry = entry?; let path = entry.path(); - let file_type = entry.file_type()?; let dest_path = dst.join(entry.file_name()); - if file_type.is_dir() { + + // See note in `snapshot_dir_recursive` about Buck2 symlink trees. + let metadata = fs::metadata(&path)?; + if metadata.is_dir() { fs::create_dir_all(&dest_path)?; copy_dir_recursive(&path, &dest_path)?; - } else if file_type.is_file() { + } else if metadata.is_file() { if let Some(parent) = dest_path.parent() { fs::create_dir_all(parent)?; }