Codex Handbook
core/tests/common/zsh_fork.rs 154 lines
use std::path::Path;use std::path::PathBuf;use anyhow::Result;use codex_core::config::Config;use codex_core::config::Constrained;use codex_features::Feature;use codex_protocol::models::PermissionProfile;use codex_protocol::permissions::NetworkSandboxPolicy;use codex_protocol::protocol::AskForApproval;use crate::test_codex::TestCodex;use crate::test_codex::test_codex;#[derive(Clone)]pub struct ZshForkRuntime {    zsh_path: PathBuf,    main_execve_wrapper_exe: PathBuf,}impl ZshForkRuntime {    fn apply_to_config(        &self,        config: &mut Config,        approval_policy: AskForApproval,        permission_profile: PermissionProfile,    ) {        config            .features            .enable(Feature::ShellTool)            .expect("test config should allow feature update");        config            .features            .enable(Feature::ShellZshFork)            .expect("test config should allow feature update");        config.zsh_path = Some(self.zsh_path.clone());        config.main_execve_wrapper_exe = Some(self.main_execve_wrapper_exe.clone());        config.permissions.allow_login_shell = false;        config.permissions.approval_policy = Constrained::allow_any(approval_policy);        config            .permissions            .set_permission_profile(permission_profile)            .expect("set permission profile");    }}pub fn restrictive_workspace_write_profile() -> PermissionProfile {    PermissionProfile::workspace_write_with(        &[],        NetworkSandboxPolicy::Restricted,        /*exclude_tmpdir_env_var*/ true,        /*exclude_slash_tmp*/ true,    )}pub fn zsh_fork_runtime(test_name: &str) -> Result<Option<ZshForkRuntime>> {    let Some(zsh_path) = find_test_zsh_path()? else {        return Ok(None);    };    if !supports_exec_wrapper_intercept(&zsh_path) {        eprintln!(            "skipping {test_name}: zsh does not support EXEC_WRAPPER intercepts ({})",            zsh_path.display()        );        return Ok(None);    }    let Ok(main_execve_wrapper_exe) = codex_utils_cargo_bin::cargo_bin("codex-execve-wrapper")    else {        eprintln!("skipping {test_name}: unable to resolve `codex-execve-wrapper` binary");        return Ok(None);    };    Ok(Some(ZshForkRuntime {        zsh_path,        main_execve_wrapper_exe,    }))}pub async fn build_zsh_fork_test<F>(    server: &wiremock::MockServer,    runtime: ZshForkRuntime,    approval_policy: AskForApproval,    permission_profile: PermissionProfile,    pre_build_hook: F,) -> Result<TestCodex>where    F: FnOnce(&Path) + Send + 'static,{    let mut builder = test_codex()        .with_pre_build_hook(pre_build_hook)        .with_config(move |config| {            runtime.apply_to_config(config, approval_policy, permission_profile);        });    builder.build(server).await}pub async fn build_unified_exec_zsh_fork_test<F>(    server: &wiremock::MockServer,    runtime: ZshForkRuntime,    approval_policy: AskForApproval,    permission_profile: PermissionProfile,    pre_build_hook: F,) -> Result<TestCodex>where    F: FnOnce(&Path) + Send + 'static,{    let mut builder = test_codex()        .with_pre_build_hook(pre_build_hook)        .with_config(move |config| {            runtime.apply_to_config(config, approval_policy, permission_profile);            config.use_experimental_unified_exec_tool = true;            config                .features                .enable(Feature::UnifiedExec)                .expect("test config should allow feature update");            config                .features                .enable(Feature::UnifiedExecZshFork)                .expect("test config should allow feature update");        });    builder.build(server).await}fn find_test_zsh_path() -> Result<Option<PathBuf>> {    let repo_root = codex_utils_cargo_bin::repo_root()?;    let dotslash_zsh = repo_root.join("codex-rs/app-server/tests/suite/zsh");    if !dotslash_zsh.is_file() {        eprintln!(            "skipping zsh-fork test: shared zsh DotSlash file not found at {}",            dotslash_zsh.display()        );        return Ok(None);    }    match crate::fetch_dotslash_file(&dotslash_zsh, /*dotslash_cache*/ None) {        Ok(path) => Ok(Some(path)),        Err(error) => {            eprintln!("skipping zsh-fork test: failed to fetch zsh via dotslash: {error:#}");            Ok(None)        }    }}fn supports_exec_wrapper_intercept(zsh_path: &Path) -> bool {    let status = std::process::Command::new(zsh_path)        .arg("-fc")        .arg("/usr/bin/true")        .env("EXEC_WRAPPER", "/usr/bin/false")        .status();    match status {        Ok(status) => !status.success(),        Err(_) => false,    }}