mirror of
https://github.com/openai/codex.git
synced 2026-04-29 00:55:38 +00:00
test: vendor zsh fork via DotSlash and stabilize zsh-fork tests (#12518)
## Why The zsh integration tests were still brittle in two ways: - they relied on `CODEX_TEST_ZSH_PATH` / environment-specific setup, so they often did not exercise the patched zsh fork that `shell-tool-mcp` ships - once the tests consistently used the vendored zsh fork, they exposed real Linux-specific zsh-fork issues in CI In particular, the Linux failures were not just test noise: - the zsh-fork launch path was dropping `ExecRequest.arg0`, so Linux `codex-linux-sandbox` arg0 dispatch did not run and zsh wrapper-mode could receive malformed arguments - the `turn_start_shell_zsh_fork_subcommand_decline_marks_parent_declined_v2` test uses the zsh exec bridge (which talks to the parent over a Unix socket), but Linux restricted sandbox seccomp denies `connect(2)`, causing timeouts on `ubuntu-24.04` x86/arm This PR makes the zsh tests consistently run against the intended vendored zsh fork and fixes/hardens the zsh-fork path so the Linux CI signal is meaningful. ## What Changed - Added a single shared test-only DotSlash file for the patched zsh fork at `codex-rs/exec-server/tests/suite/zsh` (analogous to the existing `bash` test resource). - Updated both app-server and exec-server zsh tests to use that shared DotSlash zsh (no duplicate zsh DotSlash file, no `CODEX_TEST_ZSH_PATH` dependency). - Updated the app-server zsh-fork test helper to resolve the shared DotSlash zsh and avoid silently falling back to host zsh. - Kept the app-server zsh-fork tests configured via `config.toml`, using a test wrapper path where needed to force `zsh -df` (and rewrite `-lc` to `-c`) for the subcommand-decline test. - Hardened the app-server subcommand-decline zsh-fork test for CI variability: - tolerate an extra `/responses` POST with a no-op mock response - tolerate non-target approval ordering while remaining strict on the two `/usr/bin/true` approvals and decline behavior - use `DangerFullAccess` on Linux for this one test because it validates zsh approval flow, not Linux sandbox socket restrictions - Fixed zsh-fork process launching on Linux by preserving `req.arg0` in `ZshExecBridge::execute_shell_request(...)` so `codex-linux-sandbox` arg0 dispatch continues to work. - Moved `maybe_run_zsh_exec_wrapper_mode()` under `arg0_dispatch_or_else(...)` in `app-server` and `cli` so wrapper-mode handling coexists correctly with arg0-dispatched helper modes. - Consolidated duplicated `dotslash -- fetch` resolution logic into shared test support (`core/tests/common/lib.rs`). - Updated `codex-rs/exec-server/tests/suite/accept_elicitation.rs` to use DotSlash zsh and hardened the zsh elicitation test for Bazel/zsh differences by: - resolving an absolute `git` path - running `git init --quiet .` - asserting success / `.git` creation instead of relying on banner text ## Verification - `cargo test -p codex-app-server turn_start_zsh_fork -- --nocapture` - `cargo test -p codex-exec-server accept_elicitation -- --nocapture` - `bazel test //codex-rs/exec-server:exec-server-all-test --test_output=streamed --test_arg=--nocapture --test_arg=accept_elicitation_for_prompt_rule_with_zsh` - CI (`rust-ci`) on the final cleaned commit: `Tests — ubuntu-24.04 - x86_64-unknown-linux-gnu` and `Tests — ubuntu-24.04-arm - aarch64-unknown-linux-gnu` passed in [run 22291424358](https://github.com/openai/codex/actions/runs/22291424358)
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
#![expect(clippy::expect_used)]
|
||||
|
||||
use anyhow::Context as _;
|
||||
use anyhow::ensure;
|
||||
use codex_utils_cargo_bin::CargoBinError;
|
||||
use ctor::ctor;
|
||||
use tempfile::TempDir;
|
||||
@@ -99,6 +101,42 @@ pub fn test_tmp_path_buf() -> PathBuf {
|
||||
test_tmp_path().into_path_buf()
|
||||
}
|
||||
|
||||
/// Fetch a DotSlash resource and return the resolved executable/file path.
|
||||
pub fn fetch_dotslash_file(
|
||||
dotslash_file: &std::path::Path,
|
||||
dotslash_cache: Option<&std::path::Path>,
|
||||
) -> anyhow::Result<PathBuf> {
|
||||
let mut command = std::process::Command::new("dotslash");
|
||||
command.arg("--").arg("fetch").arg(dotslash_file);
|
||||
if let Some(dotslash_cache) = dotslash_cache {
|
||||
command.env("DOTSLASH_CACHE", dotslash_cache);
|
||||
}
|
||||
let output = command.output().with_context(|| {
|
||||
format!(
|
||||
"failed to run dotslash to fetch resource {}",
|
||||
dotslash_file.display()
|
||||
)
|
||||
})?;
|
||||
ensure!(
|
||||
output.status.success(),
|
||||
"dotslash fetch failed for {}: {}",
|
||||
dotslash_file.display(),
|
||||
String::from_utf8_lossy(&output.stderr).trim()
|
||||
);
|
||||
let fetched_path = String::from_utf8(output.stdout)
|
||||
.context("dotslash fetch output was not utf8")?
|
||||
.trim()
|
||||
.to_string();
|
||||
ensure!(!fetched_path.is_empty(), "dotslash fetch output was empty");
|
||||
let fetched_path = PathBuf::from(fetched_path);
|
||||
ensure!(
|
||||
fetched_path.is_file(),
|
||||
"dotslash returned non-file path: {}",
|
||||
fetched_path.display()
|
||||
);
|
||||
Ok(fetched_path)
|
||||
}
|
||||
|
||||
/// Returns a default `Config` whose on-disk state is confined to the provided
|
||||
/// temporary directory. Using a per-test directory keeps tests hermetic and
|
||||
/// avoids clobbering a developer’s real `~/.codex`.
|
||||
|
||||
Reference in New Issue
Block a user