Download codex-rs/exec-server/tests/process.rs from SaylorTwift/codex: direct link, hf CLI and curl.
- Browser
- Download file 29.7 kB
-
https://huggingface.co/SaylorTwift/codex/resolve/main/codex-rs/exec-server/tests/process.rs
- Command line
-
hf download hf://SaylorTwift/codex/codex-rs/exec-server/tests/process.rs
-
curl -L -o process.rs https://huggingface.co/SaylorTwift/codex/resolve/main/codex-rs/exec-server/tests/process.rs
29.7 kB
| mod common; | |
| use std::collections::HashMap; | |
| use std::time::Duration; | |
| use anyhow::Context; | |
| use codex_exec_server::EnvironmentInfo; | |
| use codex_exec_server::EnvironmentStatus; | |
| use codex_exec_server::EnvironmentStatusKind; | |
| use codex_exec_server::ExecResponse; | |
| use codex_exec_server::InitializeParams; | |
| use codex_exec_server::InitializeResponse; | |
| use codex_exec_server::ProcessId; | |
| use codex_exec_server::ReadResponse; | |
| use codex_exec_server::TerminateResponse; | |
| use codex_exec_server::WriteResponse; | |
| use codex_exec_server::WriteStatus; | |
| use codex_exec_server_protocol::JSONRPCError; | |
| use codex_exec_server_protocol::JSONRPCMessage; | |
| use codex_exec_server_protocol::JSONRPCResponse; | |
| use codex_exec_server_protocol::ProcessSandboxType; | |
| use codex_utils_path_uri::PathUri; | |
| use common::exec_server::exec_server; | |
| use common::exec_server::exec_server_with_env; | |
| use pretty_assertions::assert_eq; | |
| use tokio::time::sleep; | |
| use tokio::time::timeout; | |
| async fn exec_server_starts_process_over_websocket() -> anyhow::Result<()> { | |
| let mut server = exec_server().await?; | |
| let process_argv = if cfg!(windows) { | |
| vec!["cmd.exe", "/D", "/C", "exit 0"] | |
| } else { | |
| vec!["true"] | |
| }; | |
| let initialize_id = server | |
| .send_request( | |
| "initialize", | |
| serde_json::to_value(InitializeParams { | |
| client_name: "exec-server-test".to_string(), | |
| resume_session_id: None, | |
| })?, | |
| ) | |
| .await?; | |
| let _ = server | |
| .wait_for_event(|event| { | |
| matches!( | |
| event, | |
| JSONRPCMessage::Response(JSONRPCResponse { id, .. }) if id == &initialize_id | |
| ) | |
| }) | |
| .await?; | |
| server | |
| .send_notification("initialized", serde_json::json!({})) | |
| .await?; | |
| let process_start_id = server | |
| .send_request( | |
| "process/start", | |
| serde_json::json!({ | |
| "processId": "proc-1", | |
| "argv": process_argv, | |
| "cwd": PathUri::from_host_native_path(std::env::current_dir()?)?, | |
| "env": {}, | |
| "tty": false, | |
| "pipeStdin": false, | |
| "arg0": null | |
| }), | |
| ) | |
| .await?; | |
| let response = server | |
| .wait_for_event(|event| { | |
| matches!( | |
| event, | |
| JSONRPCMessage::Response(JSONRPCResponse { id, .. }) if id == &process_start_id | |
| ) | |
| }) | |
| .await?; | |
| let JSONRPCMessage::Response(JSONRPCResponse { id, result }) = response else { | |
| panic!("expected process/start response"); | |
| }; | |
| assert_eq!(id, process_start_id); | |
| let process_start_response: ExecResponse = serde_json::from_value(result)?; | |
| assert_eq!( | |
| process_start_response, | |
| ExecResponse { | |
| process_id: ProcessId::from("proc-1"), | |
| sandbox_type: Some(ProcessSandboxType::None), | |
| } | |
| ); | |
| server.shutdown().await?; | |
| Ok(()) | |
| } | |
| /// Ordinary requests run one at a time when concurrent processing is not enabled. | |
| async fn exec_server_runs_ordinary_requests_serially_by_default() -> anyhow::Result<()> { | |
| let temporary_directory = tempfile::tempdir()?; | |
| let temporary_directory_env_vars: &[&str] = if cfg!(windows) { | |
| &["TEMP", "TMP"] | |
| } else { | |
| &["TMPDIR"] | |
| }; | |
| let mut server = exec_server_with_env( | |
| temporary_directory_env_vars | |
| .iter() | |
| .map(|name| (*name, temporary_directory.path())), | |
| &[], | |
| ) | |
| .await?; | |
| let process_argv = if cfg!(windows) { | |
| vec!["cmd.exe", "/D", "/C", "ping -n 601 127.0.0.1 >NUL"] | |
| } else { | |
| vec![ | |
| "/bin/sh", | |
| "-c", | |
| "parent=$PPID; while kill -0 \"$parent\" 2>/dev/null; do sleep 1; done", | |
| ] | |
| }; | |
| let process_env = if cfg!(windows) { | |
| serde_json::json!({ "PATH": std::env::var("PATH")? }) | |
| } else { | |
| serde_json::json!({}) | |
| }; | |
| let initialize_id = server | |
| .send_request( | |
| "initialize", | |
| serde_json::to_value(InitializeParams { | |
| client_name: "exec-server-test".to_string(), | |
| resume_session_id: None, | |
| })?, | |
| ) | |
| .await?; | |
| let response = server | |
| .wait_for_event(|event| { | |
| matches!( | |
| event, | |
| JSONRPCMessage::Response(JSONRPCResponse { id, .. }) if id == &initialize_id | |
| ) | |
| }) | |
| .await?; | |
| let JSONRPCMessage::Response(JSONRPCResponse { result, .. }) = response else { | |
| panic!("expected initialize response"); | |
| }; | |
| let initialization: InitializeResponse = serde_json::from_value(result)?; | |
| server | |
| .send_notification("initialized", serde_json::json!({})) | |
| .await?; | |
| let process_start_id = server | |
| .send_request( | |
| "process/start", | |
| serde_json::json!({ | |
| "processId": "proc-serial-read", | |
| "argv": process_argv, | |
| "cwd": PathUri::from_host_native_path(std::env::current_dir()?)?, | |
| "env": process_env, | |
| "tty": false, | |
| "pipeStdin": false, | |
| "arg0": null | |
| }), | |
| ) | |
| .await?; | |
| let _ = server | |
| .wait_for_event(|event| { | |
| matches!( | |
| event, | |
| JSONRPCMessage::Response(JSONRPCResponse { id, .. }) if id == &process_start_id | |
| ) | |
| }) | |
| .await?; | |
| let read_id = server | |
| .send_request( | |
| "process/read", | |
| serde_json::json!({ | |
| "processId": "proc-serial-read", | |
| "afterSeq": null, | |
| "maxBytes": null, | |
| "waitMs": 250 | |
| }), | |
| ) | |
| .await?; | |
| let queued_environment_info_id = server | |
| .send_request("environment/info", serde_json::json!({})) | |
| .await?; | |
| let queued_start_id = server | |
| .send_request( | |
| "process/start", | |
| serde_json::json!({ | |
| "processId": "proc-serial-queued", | |
| "argv": process_argv, | |
| "cwd": PathUri::from_host_native_path(std::env::current_dir()?)?, | |
| "env": process_env, | |
| "tty": false, | |
| "pipeStdin": false, | |
| "arg0": null | |
| }), | |
| ) | |
| .await?; | |
| let response = server | |
| .wait_for_event(|event| matches!(event, JSONRPCMessage::Response(_))) | |
| .await?; | |
| let JSONRPCMessage::Response(JSONRPCResponse { id, .. }) = response else { | |
| panic!("expected the blocked process/read to finish before the queued process/start"); | |
| }; | |
| assert_eq!(id, read_id); | |
| let response = server | |
| .wait_for_event(|event| matches!(event, JSONRPCMessage::Response(_))) | |
| .await?; | |
| let JSONRPCMessage::Response(JSONRPCResponse { id, result }) = response else { | |
| panic!("expected the queued environment/info response after process/read"); | |
| }; | |
| assert_eq!(id, queued_environment_info_id); | |
| let mut expected_environment_info = EnvironmentInfo::local(); | |
| expected_environment_info.provider_id = initialization | |
| .environment_info | |
| .and_then(|info| info.provider_id); | |
| expected_environment_info.temporary_directories = Some(vec![PathUri::from_host_native_path( | |
| temporary_directory.path(), | |
| )?]); | |
| expected_environment_info.temp_dir = | |
| Some(PathUri::from_host_native_path(temporary_directory.path())?); | |
| assert_eq!( | |
| serde_json::from_value::<EnvironmentInfo>(result)?, | |
| expected_environment_info | |
| ); | |
| let response = server | |
| .wait_for_event(|event| matches!(event, JSONRPCMessage::Response(_))) | |
| .await?; | |
| let JSONRPCMessage::Response(JSONRPCResponse { id, result }) = response else { | |
| panic!("expected the queued process/start response after process/read"); | |
| }; | |
| assert_eq!(id, queued_start_id); | |
| assert_eq!( | |
| serde_json::from_value::<ExecResponse>(result)?, | |
| ExecResponse { | |
| process_id: ProcessId::from("proc-serial-queued"), | |
| sandbox_type: Some(ProcessSandboxType::None), | |
| } | |
| ); | |
| for process_id in ["proc-serial-read", "proc-serial-queued"] { | |
| let terminate_id = server | |
| .send_request( | |
| "process/terminate", | |
| serde_json::json!({ "processId": process_id }), | |
| ) | |
| .await?; | |
| server | |
| .wait_for_event(|event| { | |
| matches!( | |
| event, | |
| JSONRPCMessage::Response(JSONRPCResponse { id, .. }) if id == &terminate_id | |
| ) | |
| }) | |
| .await?; | |
| } | |
| server.shutdown().await?; | |
| Ok(()) | |
| } | |
| /// A long read cannot block health checks; saturated ordinary work cannot block health checks or cleanup. | |
| async fn exec_server_keeps_control_requests_live_during_long_reads_and_queued_requests() | |
| -> anyhow::Result<()> { | |
| let mut server = exec_server_with_env( | |
| std::iter::empty::<(&str, &str)>(), | |
| &["--concurrent-requests", "32"], | |
| ) | |
| .await?; | |
| let process_argv = if cfg!(windows) { | |
| vec!["cmd.exe", "/D", "/C", "ping -n 601 127.0.0.1 >NUL"] | |
| } else { | |
| vec![ | |
| "/bin/sh", | |
| "-c", | |
| "parent=$PPID; while kill -0 \"$parent\" 2>/dev/null; do sleep 1; done", | |
| ] | |
| }; | |
| let process_env = if cfg!(windows) { | |
| serde_json::json!({ "PATH": std::env::var("PATH")? }) | |
| } else { | |
| serde_json::json!({}) | |
| }; | |
| let initialize_id = server | |
| .send_request( | |
| "initialize", | |
| serde_json::to_value(InitializeParams { | |
| client_name: "exec-server-test".to_string(), | |
| resume_session_id: None, | |
| })?, | |
| ) | |
| .await?; | |
| assert!(matches!( | |
| server.next_event().await?, | |
| JSONRPCMessage::Response(JSONRPCResponse { id, .. }) if id == initialize_id | |
| )); | |
| server | |
| .send_notification("initialized", serde_json::json!({})) | |
| .await?; | |
| let process_start_id = server | |
| .send_request( | |
| "process/start", | |
| serde_json::json!({ | |
| "processId": "proc-capacity", | |
| "argv": process_argv, | |
| "cwd": PathUri::from_host_native_path(std::env::current_dir()?)?, | |
| "env": process_env, | |
| "tty": false, | |
| "pipeStdin": false, | |
| "arg0": null | |
| }), | |
| ) | |
| .await?; | |
| assert!(matches!( | |
| server.next_event().await?, | |
| JSONRPCMessage::Response(JSONRPCResponse { id, .. }) if id == process_start_id | |
| )); | |
| let read_params = serde_json::json!({ | |
| "processId": "proc-capacity", | |
| "afterSeq": null, | |
| "maxBytes": null, | |
| "waitMs": 600_000 | |
| }); | |
| server | |
| .send_request("process/read", read_params.clone()) | |
| .await?; | |
| let concurrent_start_id = server | |
| .send_request( | |
| "process/start", | |
| serde_json::json!({ | |
| "processId": "proc-concurrent", | |
| "argv": process_argv, | |
| "cwd": PathUri::from_host_native_path(std::env::current_dir()?)?, | |
| "env": process_env, | |
| "tty": false, | |
| "pipeStdin": false, | |
| "arg0": null | |
| }), | |
| ) | |
| .await?; | |
| let JSONRPCMessage::Response(JSONRPCResponse { id, result }) = server.next_event().await? | |
| else { | |
| panic!("expected process/start to finish before the pending process/read"); | |
| }; | |
| assert_eq!(id, concurrent_start_id); | |
| assert_eq!( | |
| serde_json::from_value::<ExecResponse>(result)?, | |
| ExecResponse { | |
| process_id: ProcessId::from("proc-concurrent"), | |
| sandbox_type: Some(ProcessSandboxType::None), | |
| } | |
| ); | |
| for _ in 1..32 { | |
| server | |
| .send_request("process/read", read_params.clone()) | |
| .await?; | |
| } | |
| let queued_read_id = server.send_request("process/read", read_params).await?; | |
| let environment_info_id = server | |
| .send_request("environment/info", serde_json::json!({})) | |
| .await?; | |
| let response = server | |
| .wait_for_event(|event| matches!(event, JSONRPCMessage::Response(_))) | |
| .await?; | |
| let JSONRPCMessage::Response(JSONRPCResponse { id, result }) = response else { | |
| panic!("expected environment/info response at regular request capacity"); | |
| }; | |
| assert_eq!(id, environment_info_id); | |
| let _: EnvironmentInfo = serde_json::from_value(result)?; | |
| let environment_status_id = server | |
| .send_request("environment/status", serde_json::json!({})) | |
| .await?; | |
| let response = server | |
| .wait_for_event(|event| matches!(event, JSONRPCMessage::Response(_))) | |
| .await?; | |
| let JSONRPCMessage::Response(JSONRPCResponse { id, result }) = response else { | |
| panic!("expected environment/status response at regular request capacity"); | |
| }; | |
| assert_eq!(id, environment_status_id); | |
| assert_eq!( | |
| serde_json::from_value::<EnvironmentStatus>(result)?, | |
| EnvironmentStatus { | |
| status: EnvironmentStatusKind::Ready, | |
| } | |
| ); | |
| let terminate_id = server | |
| .send_request( | |
| "process/terminate", | |
| serde_json::json!({ "processId": "proc-capacity" }), | |
| ) | |
| .await?; | |
| let mut terminate_response = None; | |
| let mut queued_read_completed = false; | |
| while terminate_response.is_none() || !queued_read_completed { | |
| match server.next_event().await? { | |
| JSONRPCMessage::Response(JSONRPCResponse { id, result }) if id == terminate_id => { | |
| terminate_response = Some(serde_json::from_value::<TerminateResponse>(result)?); | |
| } | |
| JSONRPCMessage::Response(JSONRPCResponse { id, result }) if id == queued_read_id => { | |
| let _: ReadResponse = serde_json::from_value(result)?; | |
| queued_read_completed = true; | |
| } | |
| JSONRPCMessage::Error(error) => { | |
| anyhow::bail!("unexpected error while waiting for queued requests: {error:?}"); | |
| } | |
| JSONRPCMessage::Request(_) | |
| | JSONRPCMessage::Response(_) | |
| | JSONRPCMessage::Notification(_) => {} | |
| } | |
| } | |
| assert_eq!( | |
| terminate_response, | |
| Some(TerminateResponse { running: true }) | |
| ); | |
| let terminate_id = server | |
| .send_request( | |
| "process/terminate", | |
| serde_json::json!({ "processId": "proc-concurrent" }), | |
| ) | |
| .await?; | |
| server | |
| .wait_for_event(|event| { | |
| matches!( | |
| event, | |
| JSONRPCMessage::Response(JSONRPCResponse { id, .. }) if id == &terminate_id | |
| ) | |
| }) | |
| .await?; | |
| server.shutdown().await?; | |
| Ok(()) | |
| } | |
| async fn exec_server_defaults_omitted_pipe_stdin_to_closed_stdin() -> anyhow::Result<()> { | |
| let mut server = exec_server().await?; | |
| let process_argv = if cfg!(windows) { | |
| vec!["cmd.exe", "/D", "/C", "ping -n 2 127.0.0.1 >NUL"] | |
| } else { | |
| vec![ | |
| "/bin/sh", | |
| "-c", | |
| "sleep 0.3; if IFS= read -r line; then printf 'read:%s\\n' \"$line\"; else printf 'eof\\n'; fi", | |
| ] | |
| }; | |
| let process_env = if cfg!(windows) { | |
| serde_json::json!({ "PATH": std::env::var("PATH")? }) | |
| } else { | |
| serde_json::json!({}) | |
| }; | |
| let initialize_id = server | |
| .send_request( | |
| "initialize", | |
| serde_json::to_value(InitializeParams { | |
| client_name: "exec-server-test".to_string(), | |
| resume_session_id: None, | |
| })?, | |
| ) | |
| .await?; | |
| let _ = server | |
| .wait_for_event(|event| { | |
| matches!( | |
| event, | |
| JSONRPCMessage::Response(JSONRPCResponse { id, .. }) if id == &initialize_id | |
| ) | |
| }) | |
| .await?; | |
| server | |
| .send_notification("initialized", serde_json::json!({})) | |
| .await?; | |
| let process_start_id = server | |
| .send_request( | |
| "process/start", | |
| serde_json::json!({ | |
| "processId": "proc-default-stdin", | |
| "argv": process_argv, | |
| "cwd": PathUri::from_host_native_path(std::env::current_dir()?)?, | |
| "env": process_env, | |
| "tty": false, | |
| "arg0": null | |
| }), | |
| ) | |
| .await?; | |
| let response = server | |
| .wait_for_event(|event| { | |
| matches!( | |
| event, | |
| JSONRPCMessage::Response(JSONRPCResponse { id, .. }) if id == &process_start_id | |
| ) | |
| }) | |
| .await?; | |
| let JSONRPCMessage::Response(JSONRPCResponse { result, .. }) = response else { | |
| panic!("expected process/start response"); | |
| }; | |
| let process_start_response: ExecResponse = serde_json::from_value(result)?; | |
| assert_eq!( | |
| process_start_response, | |
| ExecResponse { | |
| process_id: ProcessId::from("proc-default-stdin"), | |
| sandbox_type: Some(ProcessSandboxType::None), | |
| } | |
| ); | |
| let write_id = server | |
| .send_request( | |
| "process/write", | |
| serde_json::json!({ | |
| "processId": "proc-default-stdin", | |
| "chunk": "aWdub3JlZAo=", | |
| "writeId": "write-default-stdin" | |
| }), | |
| ) | |
| .await?; | |
| let response = server | |
| .wait_for_event(|event| { | |
| matches!( | |
| event, | |
| JSONRPCMessage::Response(JSONRPCResponse { id, .. }) if id == &write_id | |
| ) | |
| }) | |
| .await?; | |
| let JSONRPCMessage::Response(JSONRPCResponse { result, .. }) = response else { | |
| panic!("expected process/write response"); | |
| }; | |
| let write_response: WriteResponse = serde_json::from_value(result)?; | |
| assert_eq!( | |
| write_response, | |
| WriteResponse { | |
| status: WriteStatus::StdinClosed | |
| } | |
| ); | |
| server.shutdown().await?; | |
| Ok(()) | |
| } | |
| async fn exec_server_dedupes_retried_process_write_ids() -> anyhow::Result<()> { | |
| let mut server = exec_server().await?; | |
| let process_argv = if cfg!(windows) { | |
| vec![ | |
| "powershell.exe", | |
| "-NoProfile", | |
| "-NonInteractive", | |
| "-Command", | |
| "[Console]::Out.WriteLine('line:' + [Console]::In.ReadLine()); [Console]::Out.WriteLine('line:' + [Console]::In.ReadLine())", | |
| ] | |
| } else { | |
| vec![ | |
| "/bin/sh", | |
| "-c", | |
| "IFS= read -r first; printf 'line:%s\\n' \"$first\"; IFS= read -r second; printf 'line:%s\\n' \"$second\"", | |
| ] | |
| }; | |
| let process_env = if cfg!(windows) { | |
| serde_json::to_value(std::env::vars().collect::<HashMap<_, _>>())? | |
| } else { | |
| serde_json::json!({}) | |
| }; | |
| let initialize_id = server | |
| .send_request( | |
| "initialize", | |
| serde_json::to_value(InitializeParams { | |
| client_name: "exec-server-test".to_string(), | |
| resume_session_id: None, | |
| })?, | |
| ) | |
| .await?; | |
| let _ = server | |
| .wait_for_event(|event| { | |
| matches!( | |
| event, | |
| JSONRPCMessage::Response(JSONRPCResponse { id, .. }) if id == &initialize_id | |
| ) | |
| }) | |
| .await?; | |
| server | |
| .send_notification("initialized", serde_json::json!({})) | |
| .await?; | |
| let process_start_id = server | |
| .send_request( | |
| "process/start", | |
| serde_json::json!({ | |
| "processId": "proc-write-id", | |
| "argv": process_argv, | |
| "cwd": PathUri::from_host_native_path(std::env::current_dir()?)?, | |
| "env": process_env, | |
| "tty": false, | |
| "pipeStdin": true, | |
| "arg0": null | |
| }), | |
| ) | |
| .await?; | |
| let _ = server | |
| .wait_for_event(|event| { | |
| matches!( | |
| event, | |
| JSONRPCMessage::Response(JSONRPCResponse { id, .. }) if id == &process_start_id | |
| ) | |
| }) | |
| .await?; | |
| for (write_id, chunk) in [ | |
| ("write-1", "Zmlyc3QK"), | |
| ("write-1", "Zmlyc3QK"), | |
| ("write-2", "c2Vjb25kCg=="), | |
| ] { | |
| let request_id = server | |
| .send_request( | |
| "process/write", | |
| serde_json::json!({ | |
| "processId": "proc-write-id", | |
| "chunk": chunk, | |
| "writeId": write_id | |
| }), | |
| ) | |
| .await?; | |
| let response = server | |
| .wait_for_event(|event| { | |
| matches!( | |
| event, | |
| JSONRPCMessage::Response(JSONRPCResponse { id, .. }) if id == &request_id | |
| ) | |
| }) | |
| .await?; | |
| let JSONRPCMessage::Response(JSONRPCResponse { result, .. }) = response else { | |
| panic!("expected process/write response"); | |
| }; | |
| let write_response: WriteResponse = serde_json::from_value(result)?; | |
| assert_eq!( | |
| write_response, | |
| WriteResponse { | |
| status: WriteStatus::Accepted | |
| } | |
| ); | |
| } | |
| let mut after_seq = None; | |
| let mut output = Vec::new(); | |
| for _ in 0..5 { | |
| let read_id = server | |
| .send_request( | |
| "process/read", | |
| serde_json::json!({ | |
| "processId": "proc-write-id", | |
| "afterSeq": after_seq, | |
| "maxBytes": null, | |
| "waitMs": 1000 | |
| }), | |
| ) | |
| .await?; | |
| let response = server | |
| .wait_for_event(|event| { | |
| matches!( | |
| event, | |
| JSONRPCMessage::Response(JSONRPCResponse { id, .. }) if id == &read_id | |
| ) | |
| }) | |
| .await?; | |
| let JSONRPCMessage::Response(JSONRPCResponse { result, .. }) = response else { | |
| panic!("expected process/read response"); | |
| }; | |
| let read_response: ReadResponse = serde_json::from_value(result)?; | |
| output.extend( | |
| read_response | |
| .chunks | |
| .into_iter() | |
| .flat_map(|chunk| chunk.chunk.into_inner()), | |
| ); | |
| after_seq = Some(read_response.next_seq.saturating_sub(1)); | |
| if read_response.closed | |
| || output.ends_with(b"line:second\n") | |
| || output.ends_with(b"line:second\r\n") | |
| { | |
| break; | |
| } | |
| } | |
| assert_eq!( | |
| String::from_utf8(output)?.replace("\r\n", "\n"), | |
| "line:first\nline:second\n".to_string() | |
| ); | |
| server.shutdown().await?; | |
| Ok(()) | |
| } | |
| async fn exec_server_resumes_detached_session_without_killing_processes() -> anyhow::Result<()> { | |
| const SESSION_ALREADY_ATTACHED_ERROR_CODE: i64 = -32010; | |
| let mut server = exec_server().await?; | |
| // Keep the process alive until the test explicitly terminates it. | |
| let process_argv = if cfg!(windows) { | |
| vec!["cmd.exe", "/D", "/C", "set /p line="] | |
| } else { | |
| vec!["/bin/sh", "-c", "IFS= read -r line"] | |
| }; | |
| let process_env = if cfg!(windows) { | |
| serde_json::json!({ "PATH": std::env::var("PATH")? }) | |
| } else { | |
| serde_json::json!({}) | |
| }; | |
| let initialize_id = server | |
| .send_request( | |
| "initialize", | |
| serde_json::to_value(InitializeParams { | |
| client_name: "exec-server-test".to_string(), | |
| resume_session_id: None, | |
| })?, | |
| ) | |
| .await?; | |
| let response = server | |
| .wait_for_event(|event| { | |
| matches!( | |
| event, | |
| JSONRPCMessage::Response(JSONRPCResponse { id, .. }) | |
| | JSONRPCMessage::Error(JSONRPCError { id, .. }) | |
| if id == &initialize_id | |
| ) | |
| }) | |
| .await?; | |
| let JSONRPCMessage::Response(JSONRPCResponse { result, .. }) = response else { | |
| anyhow::bail!("expected initialize response, got {response:?}"); | |
| }; | |
| let initialize_response: InitializeResponse = serde_json::from_value(result)?; | |
| server | |
| .send_notification("initialized", serde_json::json!({})) | |
| .await?; | |
| let process_start_id = server | |
| .send_request( | |
| "process/start", | |
| serde_json::json!({ | |
| "processId": "proc-resume", | |
| "argv": process_argv, | |
| "cwd": PathUri::from_host_native_path(std::env::current_dir()?)?, | |
| "env": process_env, | |
| "tty": false, | |
| "pipeStdin": true, | |
| "arg0": null | |
| }), | |
| ) | |
| .await?; | |
| let response = server | |
| .wait_for_event(|event| { | |
| matches!( | |
| event, | |
| JSONRPCMessage::Response(JSONRPCResponse { id, .. }) | |
| | JSONRPCMessage::Error(JSONRPCError { id, .. }) | |
| if id == &process_start_id | |
| ) | |
| }) | |
| .await?; | |
| let JSONRPCMessage::Response(_) = response else { | |
| anyhow::bail!("expected process/start response, got {response:?}"); | |
| }; | |
| server.disconnect_websocket().await?; | |
| server.reconnect_websocket().await?; | |
| // Closing the old socket does not wait for the server to detach its session. | |
| let result = timeout(Duration::from_secs(5), async { | |
| loop { | |
| let resume_initialize_id = server | |
| .send_request( | |
| "initialize", | |
| serde_json::to_value(InitializeParams { | |
| client_name: "exec-server-test".to_string(), | |
| resume_session_id: Some(initialize_response.session_id.clone()), | |
| })?, | |
| ) | |
| .await?; | |
| let response = server | |
| .wait_for_event(|event| { | |
| matches!( | |
| event, | |
| JSONRPCMessage::Response(JSONRPCResponse { id, .. }) | |
| | JSONRPCMessage::Error(JSONRPCError { id, .. }) | |
| if id == &resume_initialize_id | |
| ) | |
| }) | |
| .await?; | |
| match response { | |
| JSONRPCMessage::Response(JSONRPCResponse { result, .. }) => break Ok(result), | |
| JSONRPCMessage::Error(JSONRPCError { error, .. }) | |
| if error.code == SESSION_ALREADY_ATTACHED_ERROR_CODE => | |
| { | |
| sleep(Duration::from_millis(25)).await; | |
| } | |
| JSONRPCMessage::Error(error) => { | |
| anyhow::bail!("resume initialize failed: {error:?}"); | |
| } | |
| JSONRPCMessage::Request(_) | JSONRPCMessage::Notification(_) => { | |
| unreachable!("wait_for_event only returns the matching response or error"); | |
| } | |
| } | |
| } | |
| }) | |
| .await | |
| .context("timed out resuming exec-server session after disconnect")??; | |
| let resumed_response: InitializeResponse = serde_json::from_value(result)?; | |
| assert_eq!(resumed_response, initialize_response); | |
| server | |
| .send_notification("initialized", serde_json::json!({})) | |
| .await?; | |
| let process_read_id = server | |
| .send_request( | |
| "process/read", | |
| serde_json::json!({ | |
| "processId": "proc-resume", | |
| "afterSeq": null, | |
| "maxBytes": null, | |
| "waitMs": 0 | |
| }), | |
| ) | |
| .await?; | |
| let response = server | |
| .wait_for_event(|event| { | |
| matches!( | |
| event, | |
| JSONRPCMessage::Response(JSONRPCResponse { id, .. }) | |
| | JSONRPCMessage::Error(JSONRPCError { id, .. }) | |
| if id == &process_read_id | |
| ) | |
| }) | |
| .await?; | |
| let JSONRPCMessage::Response(JSONRPCResponse { result, .. }) = response else { | |
| anyhow::bail!("expected process/read response, got {response:?}"); | |
| }; | |
| let process_read_response: ReadResponse = serde_json::from_value(result)?; | |
| assert!(process_read_response.failure.is_none()); | |
| assert!(!process_read_response.exited); | |
| assert!(!process_read_response.closed); | |
| let terminate_id = server | |
| .send_request( | |
| "process/terminate", | |
| serde_json::json!({ | |
| "processId": "proc-resume" | |
| }), | |
| ) | |
| .await?; | |
| let response = server | |
| .wait_for_event(|event| { | |
| matches!( | |
| event, | |
| JSONRPCMessage::Response(JSONRPCResponse { id, .. }) | |
| | JSONRPCMessage::Error(JSONRPCError { id, .. }) | |
| if id == &terminate_id | |
| ) | |
| }) | |
| .await?; | |
| let JSONRPCMessage::Response(JSONRPCResponse { result, .. }) = response else { | |
| anyhow::bail!("expected process/terminate response, got {response:?}"); | |
| }; | |
| let terminate_response: TerminateResponse = serde_json::from_value(result)?; | |
| assert_eq!(terminate_response, TerminateResponse { running: true }); | |
| server.shutdown().await?; | |
| Ok(()) | |
| } | |