diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/api_ping.rs | 72 | ||||
| -rw-r--r-- | tests/live_handoff.rs | 90 |
2 files changed, 162 insertions, 0 deletions
diff --git a/tests/api_ping.rs b/tests/api_ping.rs index fcfaf41f..b86d0378 100644 --- a/tests/api_ping.rs +++ b/tests/api_ping.rs @@ -356,6 +356,78 @@ contains = ["server-reload-marker"] cleanup_spawned_herdr(child, base); } +#[cfg(target_os = "linux")] +#[test] +fn shutdown_preserves_session_after_shell_is_signaled() { + let _lock = test_lock(); + let base = unique_test_dir(); + let config_home = base.join("config"); + let runtime_dir = base.join("runtime"); + let socket_path = runtime_dir.join("herdr.sock"); + + let mut child = spawn_herdr_with_shell(&config_home, &runtime_dir, &socket_path, "/bin/sh"); + wait_for_socket(&socket_path, Duration::from_secs(5)); + + let created = send_request( + &socket_path, + &format!( + r#"{{"id":"create","method":"workspace.create","params":{{"cwd":"{}","focus":true}}}}"#, + base.display() + ), + ); + let pane_id = created["result"]["root_pane"]["pane_id"] + .as_str() + .expect("root pane id"); + let process_info = send_request( + &socket_path, + &format!( + r#"{{"id":"process","method":"pane.process_info","params":{{"pane_id":"{pane_id}"}}}}"# + ), + ); + let shell_pid = process_info["result"]["process_info"]["shell_pid"] + .as_u64() + .expect("shell pid") as libc::pid_t; + + assert_eq!(unsafe { libc::kill(shell_pid, libc::SIGHUP) }, 0); + + let deadline = Instant::now() + Duration::from_secs(2); + loop { + let panes = send_request( + &socket_path, + r#"{"id":"panes","method":"pane.list","params":{}}"#, + ); + if panes["result"]["panes"] + .as_array() + .is_some_and(Vec::is_empty) + { + break; + } + assert!(Instant::now() < deadline, "signaled pane was not removed"); + thread::sleep(Duration::from_millis(20)); + } + + let stopped = send_request( + &socket_path, + r#"{"id":"stop","method":"server.stop","params":{}}"#, + ); + assert_eq!(stopped["result"]["type"], "ok"); + child.child.wait().expect("server should stop cleanly"); + + let session: serde_json::Value = serde_json::from_slice( + &fs::read(config_home.join("herdr-dev/session.json")).expect("saved session"), + ) + .expect("valid session json"); + assert_eq!(session["workspaces"].as_array().map(Vec::len), Some(1)); + assert_eq!( + session["workspaces"][0]["tabs"][0]["panes"] + .as_object() + .map(serde_json::Map::len), + Some(1) + ); + + cleanup_spawned_herdr(child, base); +} + #[cfg(not(target_os = "macos"))] #[test] fn workspace_list_and_create_round_trip() { diff --git a/tests/live_handoff.rs b/tests/live_handoff.rs index 3838b888..034e1e89 100644 --- a/tests/live_handoff.rs +++ b/tests/live_handoff.rs @@ -612,6 +612,96 @@ fn live_server_holds_one_pty_master_fd_per_pane() { cleanup_test_base(&base); } +#[cfg(target_os = "linux")] +#[test] +fn live_handoff_unknown_pane_exit_preserves_session_on_shutdown() { + let _lock = test_lock(); + let base = unique_test_dir(); + let config_home = base.join("config"); + let runtime_dir = base.join("runtime"); + let api_socket = runtime_dir.join("herdr.sock"); + + let spawned = spawn_server(&config_home, &runtime_dir, &api_socket); + wait_for_socket(&api_socket, Duration::from_secs(10)); + register_runtime_dir(&runtime_dir); + + let created = request( + &api_socket, + serde_json::json!({ + "id": "test:workspace:create", + "method": "workspace.create", + "params": {"cwd": "/tmp", "focus": true} + }), + ); + let pane_id = created["result"]["root_pane"]["pane_id"] + .as_str() + .expect("root pane id") + .to_string(); + let old_pid = spawned.child.process_id().expect("old server pid"); + + assert_ok(request( + &api_socket, + serde_json::json!({"id":"test:handoff","method":"server.live_handoff","params":{}}), + )); + let replacement_pid = + wait_for_replacement_server_pid(&runtime_dir, old_pid, Duration::from_secs(10)); + drop(spawned); + wait_for_api(&api_socket, Duration::from_secs(10)); + + let process_info = request( + &api_socket, + serde_json::json!({ + "id": "test:process-info", + "method": "pane.process_info", + "params": {"pane_id": pane_id} + }), + ); + let shell_pid = process_info["result"]["process_info"]["shell_pid"] + .as_u64() + .expect("shell pid") as libc::pid_t; + assert_eq!(unsafe { libc::kill(shell_pid, libc::SIGHUP) }, 0); + + let deadline = Instant::now() + Duration::from_secs(2); + loop { + let panes = request( + &api_socket, + serde_json::json!({"id":"test:panes","method":"pane.list","params":{}}), + ); + if panes["result"]["panes"] + .as_array() + .is_some_and(Vec::is_empty) + { + break; + } + assert!(Instant::now() < deadline, "handoff pane was not removed"); + thread::sleep(Duration::from_millis(20)); + } + + assert_ok(request( + &api_socket, + serde_json::json!({"id":"test:stop","method":"server.stop","params":{}}), + )); + let deadline = Instant::now() + Duration::from_secs(5); + while Path::new(&format!("/proc/{replacement_pid}")).exists() { + assert!(Instant::now() < deadline, "replacement server did not stop"); + thread::sleep(Duration::from_millis(20)); + } + + let session: serde_json::Value = serde_json::from_slice( + &fs::read(config_home.join("herdr-dev/session.json")).expect("saved session"), + ) + .expect("valid session json"); + assert_eq!(session["workspaces"].as_array().map(Vec::len), Some(1)); + assert_eq!( + session["workspaces"][0]["tabs"][0]["panes"] + .as_object() + .map(serde_json::Map::len), + Some(1) + ); + + cleanup_test_base(&base); +} + #[test] fn live_handoff_preserves_named_session_socket_paths() { let _lock = test_lock(); |
