diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-08-15 15:03:59 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-08-15 15:03:59 +0300 |
| commit | b660cad80450147610091e0e5758faa6276e5983 (patch) | |
| tree | 5896dce7455f69199447520c19a40af5472e436a /tests | |
| parent | 9e6c2b4e0957498b7da884ed2789fe28b9cde9cf (diff) | |
fix(client): handle remote terminal hangup (#2827)
refs #2424
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/client_mode.rs | 59 |
1 files changed, 59 insertions, 0 deletions
diff --git a/tests/client_mode.rs b/tests/client_mode.rs index e79375cf..855fc318 100644 --- a/tests/client_mode.rs +++ b/tests/client_mode.rs @@ -1076,6 +1076,65 @@ fn read_until_client_attaches(client: &SpawnedHerdr) -> String { } #[test] +fn client_exits_cleanly_when_terminal_and_transport_hang_up() { + 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 client_socket = runtime_dir.join("herdr-client.sock"); + + let mut spawned_server = spawn_server(&config_home, &runtime_dir, &api_socket, &client_socket); + wait_for_socket(&api_socket, Duration::from_secs(10)); + wait_for_socket(&client_socket, Duration::from_secs(10)); + + let mut thin_client = spawn_client_process(&config_home, &runtime_dir, &api_socket); + read_until_client_attaches(&thin_client); + + // Freeze the client so the dead terminal and transport EOF are both + // observable when it resumes, making the `--remote` shutdown race deterministic. + let client_pid = thin_client.child.process_id().expect("thin client pid") as libc::pid_t; + assert_eq!( + unsafe { libc::kill(client_pid, libc::SIGSTOP) }, + 0, + "stop thin client" + ); + let server_pid = spawned_server.child.process_id().expect("server pid") as libc::pid_t; + assert_eq!( + unsafe { libc::kill(server_pid, libc::SIGKILL) }, + 0, + "kill server transport" + ); + spawned_server.close_master(); + thin_client.close_master(); + assert_eq!( + unsafe { libc::kill(client_pid, libc::SIGCONT) }, + 0, + "resume thin client" + ); + + let deadline = Instant::now() + Duration::from_secs(12); + let status = loop { + if let Some(status) = thin_client.child.try_wait().expect("poll thin client") { + break Some(status); + } + if Instant::now() >= deadline { + break None; + } + thread::sleep(Duration::from_millis(20)); + }; + + drop(spawned_server); + cleanup_spawned_herdr(thin_client, base); + + let status = status.expect("thin client should exit after terminal and transport hang up"); + assert!( + status.success(), + "thin client should exit cleanly after terminal and transport hang up, got {status}" + ); +} + +#[test] fn client_exits_cleanly_when_terminal_hangs_up() { let _lock = test_lock(); let base = unique_test_dir(); |
