use super::*;use crate::unified_exec::clamp_yield_time;use pretty_assertions::assert_eq;use tokio::time::Duration;use tokio::time::Instant;#[test]fn unified_exec_env_injects_defaults() { let env = apply_unified_exec_env(HashMap::new()); let expected = HashMap::from([ ("NO_COLOR".to_string(), "1".to_string()), ("TERM".to_string(), "dumb".to_string()), ("LANG".to_string(), "C.UTF-8".to_string()), ("LC_CTYPE".to_string(), "C.UTF-8".to_string()), ("LC_ALL".to_string(), "C.UTF-8".to_string()), ("COLORTERM".to_string(), String::new()), ("PAGER".to_string(), "cat".to_string()), ("GIT_PAGER".to_string(), "cat".to_string()), ("GH_PAGER".to_string(), "cat".to_string()), ("CODEX_CI".to_string(), "1".to_string()), ]); assert_eq!(env, expected);}#[test]fn unified_exec_env_overrides_existing_values() { let mut base = HashMap::new(); base.insert("NO_COLOR".to_string(), "0".to_string()); base.insert("PATH".to_string(), "/usr/bin".to_string()); let env = apply_unified_exec_env(base); assert_eq!(env.get("NO_COLOR"), Some(&"1".to_string())); assert_eq!(env.get("PATH"), Some(&"/usr/bin".to_string()));}#[test]fn env_overlay_for_exec_server_keeps_runtime_changes_only() { let local_policy_env = HashMap::from([ ("HOME".to_string(), "/client-home".to_string()), ("PATH".to_string(), "/client-path".to_string()), ("SHELL_SET".to_string(), "policy".to_string()), ]); let request_env = HashMap::from([ ("HOME".to_string(), "/client-home".to_string()), ("PATH".to_string(), "/sandbox-path".to_string()), ("SHELL_SET".to_string(), "policy".to_string()), ("CODEX_THREAD_ID".to_string(), "thread-1".to_string()), ( "CODEX_SANDBOX_NETWORK_DISABLED".to_string(), "1".to_string(), ), ]); assert_eq!( env_overlay_for_exec_server(&request_env, &local_policy_env), HashMap::from([ ("PATH".to_string(), "/sandbox-path".to_string()), ("CODEX_THREAD_ID".to_string(), "thread-1".to_string()), ( "CODEX_SANDBOX_NETWORK_DISABLED".to_string(), "1".to_string() ), ]) );}#[test]fn exec_server_params_use_path_uri_and_env_policy_overlay_contract() { let cwd: codex_utils_absolute_path::AbsolutePathBuf = std::env::current_dir() .expect("current dir") .try_into() .expect("absolute path"); let file_system_sandbox_policy = codex_protocol::permissions::FileSystemSandboxPolicy::unrestricted(); let network_sandbox_policy = codex_protocol::permissions::NetworkSandboxPolicy::Restricted; let permission_profile = codex_protocol::models::PermissionProfile::Disabled; let request = ExecRequest { command: vec!["bash".to_string(), "-lc".to_string(), "true".to_string()], cwd: cwd.clone(), env: HashMap::from([ ("HOME".to_string(), "/client-home".to_string()), ("PATH".to_string(), "/sandbox-path".to_string()), ("CODEX_THREAD_ID".to_string(), "thread-1".to_string()), ]), exec_server_env_config: Some(ExecServerEnvConfig { policy: codex_exec_server::ExecEnvPolicy { inherit: codex_protocol::config_types::ShellEnvironmentPolicyInherit::Core, ignore_default_excludes: false, exclude: Vec::new(), r#set: HashMap::new(), include_only: Vec::new(), }, local_policy_env: HashMap::from([ ("HOME".to_string(), "/client-home".to_string()), ("PATH".to_string(), "/client-path".to_string()), ]), }), network: None, expiration: crate::exec::ExecExpiration::DefaultTimeout, capture_policy: crate::exec::ExecCapturePolicy::ShellTool, sandbox: codex_sandboxing::SandboxType::None, windows_sandbox_policy_cwd: cwd.clone(), windows_sandbox_workspace_roots: vec![cwd], windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel::Disabled, windows_sandbox_private_desktop: false, permission_profile, file_system_sandbox_policy, network_sandbox_policy, windows_sandbox_filesystem_overrides: None, arg0: None, }; let params = exec_server_params_for_request(/*process_id*/ 123, &request, /*tty*/ true); assert_eq!(params.process_id.as_str(), "123"); assert_eq!(params.cwd, PathUri::from_abs_path(&request.cwd)); assert!(params.env_policy.is_some()); assert_eq!( params.env, HashMap::from([ ("PATH".to_string(), "/sandbox-path".to_string()), ("CODEX_THREAD_ID".to_string(), "thread-1".to_string()), ]) );}#[test]fn exec_server_process_id_matches_unified_exec_process_id() { assert_eq!(exec_server_process_id(/*process_id*/ 4321), "4321");}#[cfg(windows)]#[test]fn initial_exec_yield_time_uses_windows_floor() { let above_max_yield_time_ms = crate::unified_exec::MAX_YIELD_TIME_MS + 1; assert_eq!( clamp_yield_time(/*yield_time_ms*/ 1_000), crate::unified_exec::WINDOWS_INITIAL_EXEC_YIELD_TIME_FLOOR_MS ); assert_eq!(clamp_yield_time(/*yield_time_ms*/ 10_000), 10_000); assert_eq!( clamp_yield_time(/*yield_time_ms*/ above_max_yield_time_ms), crate::unified_exec::MAX_YIELD_TIME_MS );}#[cfg(not(windows))]#[test]fn initial_exec_yield_time_has_no_platform_floor() { assert_eq!(clamp_yield_time(/*yield_time_ms*/ 1_000), 1_000); assert_eq!( clamp_yield_time(/*yield_time_ms*/ 1), crate::unified_exec::MIN_YIELD_TIME_MS );}#[tokio::test]async fn network_denial_fallback_message_names_sandbox_network_proxy() { let message = network_denial_message_for_session(/*session*/ None, /*deferred*/ None).await; assert_eq!( message, "Network access was denied by the Codex sandbox network proxy." );}#[tokio::test]async fn late_network_denial_grace_observes_cancellation_after_exit() { let cancellation = CancellationToken::new(); let cancellation_for_task = cancellation.clone(); tokio::spawn(async move { tokio::time::sleep(Duration::from_millis(10)).await; cancellation_for_task.cancel(); }); assert!(wait_for_late_network_denial(Some(cancellation)).await);}#[tokio::test]async fn failed_initial_end_for_unstored_process_uses_fallback_output() { let (session, turn, rx_event) = crate::session::tests::make_session_and_context_with_rx().await; let context = UnifiedExecContext::new( Arc::clone(&session), Arc::clone(&turn), "call-unified-denied".to_string(), ); let request = ExecCommandRequest { command: vec![ "sh".to_string(), "-lc".to_string(), "echo before".to_string(), ], shell_type: crate::shell::ShellType::Sh, hook_command: "echo before".to_string(), process_id: 123, yield_time_ms: 1000, max_output_tokens: None, #[allow(deprecated)] cwd: turn.cwd.clone(), #[allow(deprecated)] sandbox_cwd: turn.cwd.clone(), turn_environment: turn .environments .primary() .cloned() .expect("primary environment"), shell_mode: codex_tools::UnifiedExecShellMode::Direct, network: None, tty: true, sandbox_permissions: crate::sandboxing::SandboxPermissions::UseDefault, additional_permissions: None, additional_permissions_preapproved: false, justification: None, prefix_rule: None, }; let transcript = Arc::new(tokio::sync::Mutex::new(HeadTailBuffer::default())); transcript .lock() .await .push_chunk(b"PARTIAL_TRANSCRIPT".to_vec()); emit_failed_initial_exec_end_if_unstored( /*process_started_alive*/ false, &context, &request, #[allow(deprecated)] turn.cwd.clone(), transcript, "PRE_DENIAL_MARKER".to_string(), "Network access denied".to_string(), Duration::from_millis(7), ) .await; let event = tokio::time::timeout(Duration::from_secs(1), rx_event.recv()) .await .expect("timed out waiting for failed exec end event") .expect("event channel closed"); let codex_protocol::protocol::EventMsg::ExecCommandEnd(end_event) = event.msg else { panic!("expected ExecCommandEnd event"); }; assert_eq!(end_event.call_id, "call-unified-denied"); assert_eq!( end_event.status, codex_protocol::protocol::ExecCommandStatus::Failed ); assert_eq!(end_event.exit_code, -1); assert_eq!(end_event.process_id.as_deref(), Some("123")); assert_eq!( end_event.aggregated_output, "PRE_DENIAL_MARKER\nNetwork access denied" );}#[test]fn pruning_prefers_exited_processes_outside_recently_used() { let now = Instant::now(); let meta = vec![ (1, now - Duration::from_secs(40), false), (2, now - Duration::from_secs(30), true), (3, now - Duration::from_secs(20), false), (4, now - Duration::from_secs(19), false), (5, now - Duration::from_secs(18), false), (6, now - Duration::from_secs(17), false), (7, now - Duration::from_secs(16), false), (8, now - Duration::from_secs(15), false), (9, now - Duration::from_secs(14), false), (10, now - Duration::from_secs(13), false), ]; let candidate = UnifiedExecProcessManager::process_id_to_prune_from_meta(&meta); assert_eq!(candidate, Some(2));}#[test]fn pruning_falls_back_to_lru_when_no_exited() { let now = Instant::now(); let meta = vec![ (1, now - Duration::from_secs(40), false), (2, now - Duration::from_secs(30), false), (3, now - Duration::from_secs(20), false), (4, now - Duration::from_secs(19), false), (5, now - Duration::from_secs(18), false), (6, now - Duration::from_secs(17), false), (7, now - Duration::from_secs(16), false), (8, now - Duration::from_secs(15), false), (9, now - Duration::from_secs(14), false), (10, now - Duration::from_secs(13), false), ]; let candidate = UnifiedExecProcessManager::process_id_to_prune_from_meta(&meta); assert_eq!(candidate, Some(1));}#[test]fn pruning_protects_recent_processes_even_if_exited() { let now = Instant::now(); let meta = vec![ (1, now - Duration::from_secs(40), false), (2, now - Duration::from_secs(30), false), (3, now - Duration::from_secs(20), true), (4, now - Duration::from_secs(19), false), (5, now - Duration::from_secs(18), false), (6, now - Duration::from_secs(17), false), (7, now - Duration::from_secs(16), false), (8, now - Duration::from_secs(15), false), (9, now - Duration::from_secs(14), false), (10, now - Duration::from_secs(13), true), ]; let candidate = UnifiedExecProcessManager::process_id_to_prune_from_meta(&meta); // (10) is exited but among the last 8; we should drop the LRU outside that set. assert_eq!(candidate, Some(1));}