diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-09-07 16:14:46 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-09-07 15:14:46 +0200 |
| commit | e7d8220788b4fa7c266a1e4457591b694478771c (patch) | |
| tree | 272303d74e0b58c11f9c0c434dae8335d3d06703 /tests/live_handoff.rs | |
| parent | 482818130407691154d1635d4029b95d0e730104 (diff) | |
fix: preserve sessions after interrupted pane exits (#3418)
* fix: preserve sessions when pane shells are signaled
refs #3415
* fix: preserve shutdown checkpoints across platforms
refs #3415
---------
Co-authored-by: Jonathan Liebig <jonathan.liebig@gmail.com>
Diffstat (limited to 'tests/live_handoff.rs')
| -rw-r--r-- | tests/live_handoff.rs | 90 |
1 files changed, 90 insertions, 0 deletions
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(); |
