diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/main.rs | 17 | ||||
| -rw-r--r-- | src/remote.rs | 88 | ||||
| -rw-r--r-- | src/server/handoff.rs | 36 | ||||
| -rw-r--r-- | src/server/headless.rs | 10 | ||||
| -rw-r--r-- | src/update.rs | 208 |
5 files changed, 295 insertions, 64 deletions
diff --git a/src/main.rs b/src/main.rs index 9a33555e..28dfd29d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -345,7 +345,19 @@ fn main() -> io::Result<()> { } if args.get(1).map(|s| s.as_str()) == Some("update") { - match update::self_update() { + let options = match update::parse_self_update_args(&args[2..]) { + Ok(options) => options, + Err(err) if err.starts_with("usage:") => { + eprintln!("{err}"); + std::process::exit(0); + } + Err(err) => { + eprintln!("{err}"); + eprintln!("usage: herdr update [--handoff]"); + std::process::exit(2); + } + }; + match update::self_update(options) { Ok(_) => return Ok(()), Err(e) => { if e.starts_with("self-update is disabled") { @@ -365,7 +377,7 @@ fn main() -> io::Result<()> { println!(" herdr --session <name> [options]"); println!(" herdr --remote <ssh-target> [--session <name>]"); println!(" herdr session attach <name>"); - println!(" herdr update"); + println!(" herdr update [--handoff]"); println!(" herdr server stop"); println!(" herdr server reload-config"); println!(" herdr config <subcommand> ..."); @@ -440,6 +452,7 @@ fn main() -> io::Result<()> { println!(" --remote <target> Attach through SSH to a remote Herdr server"); println!(" --remote-keybindings <local|server>"); println!(" Keybindings for --remote app attach (default: local)"); + println!(" --handoff Opt into live handoff for update or remote attach"); println!(" --default-config Print default configuration and exit"); println!(" --version, -V Print version and exit"); println!(" --help, -h Show this help"); diff --git a/src/remote.rs b/src/remote.rs index 990ffb7b..9a4cc774 100644 --- a/src/remote.rs +++ b/src/remote.rs @@ -54,6 +54,7 @@ impl RemoteKeybindings { pub(crate) struct RemoteLaunch { pub(crate) target: String, pub(crate) keybindings: RemoteKeybindings, + pub(crate) live_handoff: bool, } pub(crate) fn extract_remote_args( @@ -67,9 +68,15 @@ pub(crate) fn extract_remote_args( let mut remote_target = None; let mut keybindings = RemoteKeybindings::Local; let mut keybindings_seen = false; + let mut live_handoff = false; let mut index = 1; while index < args.len() { let arg = &args[index]; + if arg == "--handoff" { + live_handoff = true; + index += 1; + continue; + } if arg == "--remote" { if remote_target.is_some() { return Err("--remote can only be specified once".to_string()); @@ -118,10 +125,14 @@ pub(crate) fn extract_remote_args( let remote = remote_target.map(|target| RemoteLaunch { target, keybindings, + live_handoff, }); if remote.is_none() && keybindings_seen { return Err("--remote-keybindings requires --remote".to_string()); } + if remote.is_none() && live_handoff { + cleaned.push("--handoff".to_string()); + } Ok((cleaned, remote)) } @@ -143,13 +154,19 @@ pub(crate) fn run_remote(remote: RemoteLaunch) -> io::Result<()> { let program = std::env::args() .next() .unwrap_or_else(|| "herdr".to_string()); - let reattach_command = - reattach_command(&program, &remote.target, &session_name, remote.keybindings); - let prepared_remote = prepare_remote_herdr(&remote.target)?; + let reattach_command = reattach_command( + &program, + &remote.target, + &session_name, + remote.keybindings, + remote.live_handoff, + ); + let prepared_remote = prepare_remote_herdr(&remote.target, remote.live_handoff)?; ensure_remote_server_ready( &remote.target, &prepared_remote.remote_herdr, prepared_remote.installed_or_replaced, + remote.live_handoff, )?; let _bridge = SshStdioBridge::start( @@ -318,7 +335,10 @@ impl InstallSource { } } -fn prepare_remote_herdr(target: &str) -> io::Result<PreparedRemoteHerdr> { +fn prepare_remote_herdr( + target: &str, + live_handoff_enabled: bool, +) -> io::Result<PreparedRemoteHerdr> { let platform = detect_remote_platform(target)?; let remote_herdr = RemoteHerdr::for_platform(platform); let override_binary = remote_binary_override_path()?; @@ -347,7 +367,11 @@ fn prepare_remote_herdr(target: &str) -> io::Result<PreparedRemoteHerdr> { .ok() .and_then(|exists| exists.then_some(&remote_herdr)) }) { - confirm_remote_install_with_running_server(target, status_probe_herdr)?; + confirm_remote_install_with_running_server( + target, + status_probe_herdr, + live_handoff_enabled, + )?; } confirm_remote_install( target, @@ -552,6 +576,7 @@ fn ensure_remote_server_ready( target: &str, remote_herdr: &RemoteHerdr, remote_binary_changed: bool, + live_handoff_enabled: bool, ) -> io::Result<()> { let status = remote_server_status(target, remote_herdr)?; let RemoteServerStatus::Running { @@ -569,7 +594,9 @@ fn ensure_remote_server_ready( return Ok(()); }; - if live_handoff && confirm_remote_server_handoff(target, version.as_deref(), protocol, reason)? + if live_handoff_enabled + && live_handoff + && confirm_remote_server_handoff(target, version.as_deref(), protocol, reason)? { match live_handoff_remote_server(target, remote_herdr) { Ok(()) => return Ok(()), @@ -606,6 +633,7 @@ fn remote_server_restart_reason( fn confirm_remote_install_with_running_server( target: &str, remote_herdr: &RemoteHerdr, + live_handoff_enabled: bool, ) -> io::Result<()> { let status = match remote_server_status(target, remote_herdr) { Ok(status) => status, @@ -641,13 +669,13 @@ fn confirm_remote_install_with_running_server( else { return Ok(()); }; - if live_handoff { + if live_handoff_enabled && live_handoff { return Ok(()); } if !io::stdin().is_terminal() { return Err(io::Error::other(format!( - "remote herdr server on {target} is running v{} protocol {}, but it does not advertise live handoff; run from an interactive terminal to approve updating the remote binary", + "remote herdr server on {target} is running v{} protocol {}; run from an interactive terminal to approve updating the remote binary", version_label(version.as_deref()), protocol_label(protocol) ))); @@ -660,10 +688,7 @@ fn confirm_remote_install_with_running_server( protocol_label(protocol) ); eprintln!( - "this server does not advertise live handoff, so this attach cannot preserve its running panes during the update." - ); - eprintln!( - "future remote updates can preserve panes after the remote server has run a handoff-capable version once." + "this attach will not preserve running panes unless you pass --handoff and the remote server supports live handoff." ); eprintln!(); eprint!("continue installing the remote herdr binary? [Y/n] "); @@ -1118,6 +1143,7 @@ fn reattach_command( target: &str, session_name: &str, keybindings: RemoteKeybindings, + live_handoff: bool, ) -> String { let program = if program.is_empty() { "herdr" } else { program }; let mut command = format!("{} --remote {}", shell_quote(program), shell_quote(target)); @@ -1125,6 +1151,9 @@ fn reattach_command( command.push_str(" --remote-keybindings "); command.push_str(keybindings.as_str()); } + if live_handoff { + command.push_str(" --handoff"); + } if session_name != crate::session::DEFAULT_SESSION_NAME { command.push_str(" --session "); command.push_str(&shell_quote(session_name)); @@ -1471,6 +1500,28 @@ mod tests { } #[test] + fn extract_remote_args_accepts_explicit_handoff() { + let args = vec!["herdr".into(), "--remote=dev".into(), "--handoff".into()]; + + let (cleaned, remote) = extract_remote_args(&args).unwrap(); + + assert_eq!(cleaned, vec!["herdr"]); + let remote = remote.unwrap(); + assert_eq!(remote.target, "dev"); + assert!(remote.live_handoff); + } + + #[test] + fn extract_remote_args_preserves_handoff_without_remote() { + let args = vec!["herdr".into(), "update".into(), "--handoff".into()]; + + let (cleaned, remote) = extract_remote_args(&args).unwrap(); + + assert_eq!(cleaned, args); + assert!(remote.is_none()); + } + + #[test] fn extract_remote_args_rejects_remote_keybindings_without_remote() { let args = vec!["herdr".into(), "--remote-keybindings=server".into()]; let err = extract_remote_args(&args).unwrap_err(); @@ -1551,6 +1602,7 @@ mod tests { "user@host", "work", RemoteKeybindings::Local, + false, ), "target/release/herdr --remote user@host --session work" ); @@ -1560,6 +1612,7 @@ mod tests { "host name", crate::session::DEFAULT_SESSION_NAME, RemoteKeybindings::Local, + false, ), "herdr --remote 'host name'" ); @@ -1569,9 +1622,20 @@ mod tests { "host", crate::session::DEFAULT_SESSION_NAME, RemoteKeybindings::Server, + false, ), "herdr --remote host --remote-keybindings server" ); + assert_eq!( + reattach_command( + "herdr", + "host", + crate::session::DEFAULT_SESSION_NAME, + RemoteKeybindings::Local, + true, + ), + "herdr --remote host --handoff" + ); } #[test] diff --git a/src/server/handoff.rs b/src/server/handoff.rs index d38bdc29..6dadfdf4 100644 --- a/src/server/handoff.rs +++ b/src/server/handoff.rs @@ -9,7 +9,7 @@ use std::os::unix::process::CommandExt; #[cfg(unix)] use std::path::{Path, PathBuf}; #[cfg(unix)] -use std::process::Command; +use std::process::{Child, Command}; #[cfg(unix)] use std::time::Duration; @@ -71,7 +71,7 @@ pub(crate) fn spawn_handoff_import( import_exe: Option<&Path>, socket_path: &Path, token: &str, -) -> io::Result<u32> { +) -> io::Result<Child> { let fallback_exe; let exe = if let Some(import_exe) = import_exe { import_exe @@ -94,7 +94,7 @@ pub(crate) fn spawn_handoff_import( .stdin(std::process::Stdio::null()) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()); - let child = command.spawn().map_err(|err| { + command.spawn().map_err(|err| { io::Error::new( err.kind(), format!( @@ -102,8 +102,34 @@ pub(crate) fn spawn_handoff_import( exe.display() ), ) - })?; - Ok(child.id()) + }) +} + +#[cfg(unix)] +pub(crate) fn cleanup_failed_import_child(child: &mut Child) { + let pid = child.id(); + match child.try_wait() { + Ok(Some(status)) => { + info!(pid, status = %status, "handoff import server exited during rollback"); + return; + } + Ok(None) => {} + Err(err) => { + warn!(pid, err = %err, "failed to inspect handoff import server before rollback"); + } + } + + if let Err(err) = child.kill() { + warn!(pid, err = %err, "failed to kill handoff import server during rollback"); + } + match child.wait() { + Ok(status) => { + info!(pid, status = %status, "handoff import server reaped during rollback"); + } + Err(err) => { + warn!(pid, err = %err, "failed to reap handoff import server during rollback"); + } + } } #[cfg(unix)] diff --git a/src/server/headless.rs b/src/server/headless.rs index bde4d362..2b8550b4 100644 --- a/src/server/headless.rs +++ b/src/server/headless.rs @@ -655,17 +655,18 @@ impl HeadlessServer { params.expected_protocol, params.expected_version, ); - let child_pid = match crate::server::handoff::spawn_handoff_import( + let mut import_child = match crate::server::handoff::spawn_handoff_import( import_exe.as_deref(), &socket_path, &token, ) { - Ok(child_pid) => child_pid, + Ok(child) => child, Err(err) => { self.rollback_handoff_before_commit(&socket_path, &paused_terminal_ids); return Err(err); } }; + let child_pid = import_child.id(); info!(pid = child_pid, socket = %socket_path.display(), "spawned handoff import server"); let mut fds = Vec::new(); @@ -682,6 +683,7 @@ impl HeadlessServer { for fd in fds { let _ = unsafe { libc::close(fd) }; } + crate::server::handoff::cleanup_failed_import_child(&mut import_child); self.rollback_handoff_before_commit(&socket_path, &paused_terminal_ids); return Err(err); } @@ -697,6 +699,7 @@ impl HeadlessServer { for fd in fds { let _ = unsafe { libc::close(fd) }; } + crate::server::handoff::cleanup_failed_import_child(&mut import_child); self.rollback_handoff_before_commit(&socket_path, &paused_terminal_ids); return Err(err); } @@ -707,6 +710,7 @@ impl HeadlessServer { let _ = unsafe { libc::close(fd) }; } if let Err(err) = send_result { + crate::server::handoff::cleanup_failed_import_child(&mut import_child); self.rollback_handoff_before_commit(&socket_path, &paused_terminal_ids); return Err(err); } @@ -718,6 +722,7 @@ impl HeadlessServer { } let _ = remove_socket_file_if_owned(&self.client_socket_path, self.client_socket_identity); if let Err(err) = crate::server::handoff::wait_ready(&mut stream) { + crate::server::handoff::cleanup_failed_import_child(&mut import_child); match self.wait_then_restore_public_sockets_after_failed_handoff() { Ok(()) => { self.rollback_handoff_before_commit(&socket_path, &paused_terminal_ids); @@ -734,6 +739,7 @@ impl HeadlessServer { ))); } if let Err(err) = crate::server::handoff::report_committed(&mut stream) { + crate::server::handoff::cleanup_failed_import_child(&mut import_child); match self.wait_then_restore_public_sockets_after_failed_handoff() { Ok(()) => { self.rollback_handoff_before_commit(&socket_path, &paused_terminal_ids); diff --git a/src/update.rs b/src/update.rs index afd13bb4..afbbc662 100644 --- a/src/update.rs +++ b/src/update.rs @@ -27,6 +27,7 @@ const FAKE_UPDATE_VERSION_ENV: &str = "HERDR_FAKE_UPDATE_VERSION"; const FAKE_UPDATE_NOTES_VERSION_ENV: &str = "HERDR_FAKE_UPDATE_NOTES_VERSION"; const DEFAULT_FAKE_UPDATE_NOTES_VERSION: &str = "0.3.0"; const SERVER_STOP_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5); +const SERVER_HANDOFF_REQUEST_TIMEOUT: Duration = Duration::from_secs(240); const SERVER_HANDOFF_CONFIRM_TIMEOUT: Duration = Duration::from_secs(30); const SERVER_SHUTDOWN_POLL_INTERVAL: Duration = Duration::from_millis(100); const STAR_PROMPT_REPO: &str = "ogulcancelik/herdr"; @@ -422,7 +423,10 @@ fn version_label(version: Option<&str>) -> &str { version.unwrap_or("unknown") } -fn update_requires_live_handoff(server: &crate::api::RuntimeStatus, release: &ReleaseInfo) -> bool { +fn update_requires_server_restart( + server: &crate::api::RuntimeStatus, + release: &ReleaseInfo, +) -> bool { match (server.protocol, release.target_protocol) { (Some(server_protocol), Some(target_protocol)) => server_protocol != target_protocol, _ => true, @@ -449,7 +453,7 @@ fn parse_live_handoff_before_update_response(input: &str) -> Option<bool> { struct RunningServerUpdatePlan { target: RunningUpdateTarget, server: crate::api::RuntimeStatus, - requires_live_handoff: bool, + requires_server_restart: bool, } impl RunningServerUpdatePlan { @@ -558,7 +562,7 @@ fn plan_running_server_updates( }; plans.push(RunningServerUpdatePlan { - requires_live_handoff: update_requires_live_handoff(&server, release), + requires_server_restart: update_requires_server_restart(&server, release), server, target, }); @@ -670,19 +674,38 @@ fn target_client_protocol_server_is_running() -> Result<bool, String> { })) } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub(crate) struct SelfUpdateOptions { + pub(crate) live_handoff: bool, +} + +pub(crate) fn parse_self_update_args(args: &[String]) -> Result<SelfUpdateOptions, String> { + let mut options = SelfUpdateOptions::default(); + for arg in args { + match arg.as_str() { + "--handoff" => options.live_handoff = true, + "--help" | "-h" => { + return Err("usage: herdr update [--handoff]".to_string()); + } + _ => return Err(format!("unknown update option: {arg}")), + } + } + Ok(options) +} + fn prompt_to_stop_old_servers_before_update( plans: &[RunningServerUpdatePlan], release: &ReleaseInfo, ) -> Result<bool, String> { if !io::stdin().is_terminal() { return Err( - "one or more herdr targets are running and cannot perform live handoff for this update; run `herdr update` from an interactive terminal, or stop those targets and run `herdr update` again" + "one or more herdr targets must restart for this update; run `herdr update` from an interactive terminal, or stop those targets and run `herdr update` again" .to_string(), ); } eprintln!( - "these running herdr targets are too old to preserve panes during this update to v{}:", + "these running herdr targets must restart to use v{}:", release.version ); for plan in plans { @@ -693,9 +716,8 @@ fn prompt_to_stop_old_servers_before_update( protocol_label(plan.server.protocol) ); } - eprintln!( - "herdr can leave them running, or stop them after installing the update. stopping them will exit their pane processes." - ); + eprintln!("herdr can leave them running, or stop them after installing the update."); + eprintln!("stopping them will exit their pane processes."); loop { eprint!("stop these old targets after updating? [y/N] "); @@ -722,12 +744,37 @@ fn prompt_to_stop_old_servers_before_update( fn confirm_running_server_update_action( plans: Vec<RunningServerUpdatePlan>, release: &ReleaseInfo, + options: SelfUpdateOptions, ) -> Result<Vec<RunningServerUpdateDecision>, String> { if plans.is_empty() { return Ok(Vec::new()); } - print_running_session_update_summary(&plans, release); + print_running_session_update_summary(&plans, release, options); + + if !options.live_handoff { + let restart_required: Vec<RunningServerUpdatePlan> = plans + .iter() + .filter(|plan| plan.requires_server_restart) + .cloned() + .collect(); + let stop_restart_required = if restart_required.is_empty() { + false + } else { + prompt_to_stop_old_servers_before_update(&restart_required, release)? + }; + return Ok(plans + .into_iter() + .map(|plan| { + let action = if plan.requires_server_restart && stop_restart_required { + RunningServerUpdateAction::StopOldServer + } else { + RunningServerUpdateAction::None + }; + RunningServerUpdateDecision { plan, action } + }) + .collect()); + } let handoff_supported: Vec<&RunningServerUpdatePlan> = plans .iter() @@ -735,7 +782,7 @@ fn confirm_running_server_update_action( .collect(); let handoff_unsupported_requiring_update: Vec<&RunningServerUpdatePlan> = plans .iter() - .filter(|plan| !server_supports_live_handoff(&plan.server) && plan.requires_live_handoff) + .filter(|plan| !server_supports_live_handoff(&plan.server) && plan.requires_server_restart) .collect(); let live_handoff = if handoff_supported.is_empty() { @@ -744,7 +791,7 @@ fn confirm_running_server_update_action( prompt_to_live_handoff_sessions_before_update( &handoff_supported, release, - plans.iter().any(|plan| plan.requires_live_handoff), + plans.iter().any(|plan| plan.requires_server_restart), )? }; @@ -763,7 +810,7 @@ fn confirm_running_server_update_action( let action = if server_supports_live_handoff(&plan.server) && live_handoff { RunningServerUpdateAction::LiveHandoff } else if !server_supports_live_handoff(&plan.server) - && plan.requires_live_handoff + && plan.requires_server_restart && stop_unsupported { RunningServerUpdateAction::StopOldServer @@ -776,21 +823,34 @@ fn confirm_running_server_update_action( Ok(decisions) } -fn print_running_session_update_summary(plans: &[RunningServerUpdatePlan], release: &ReleaseInfo) { +fn print_running_session_update_summary( + plans: &[RunningServerUpdatePlan], + release: &ReleaseInfo, + options: SelfUpdateOptions, +) { eprintln!("running herdr targets:"); for plan in plans { - let capability = if server_supports_live_handoff(&plan.server) { - "handoff supported" + if options.live_handoff { + let capability = if server_supports_live_handoff(&plan.server) { + "handoff supported" + } else { + "too old for handoff" + }; + eprintln!( + " {}: v{} protocol {} ({})", + plan.label(), + version_label(plan.server.version.as_deref()), + protocol_label(plan.server.protocol), + capability + ); } else { - "too old for handoff" - }; - eprintln!( - " {}: v{} protocol {} ({})", - plan.label(), - version_label(plan.server.version.as_deref()), - protocol_label(plan.server.protocol), - capability - ); + eprintln!( + " {}: v{} protocol {}", + plan.label(), + version_label(plan.server.version.as_deref()), + protocol_label(plan.server.protocol) + ); + } } eprintln!( " update: v{} protocol {}", @@ -1130,7 +1190,7 @@ fn live_handoff_server_via_api_for_update_at( ) -> Result<(), String> { live_handoff_server_via_api_for_release_at( socket_path, - SERVER_HANDOFF_CONFIRM_TIMEOUT, + SERVER_HANDOFF_REQUEST_TIMEOUT, updated_exe, release, ) @@ -1679,7 +1739,7 @@ fn maybe_offer_star_after_successful_update() { // --------------------------------------------------------------------------- /// Manual self-update command (`herdr update`). -pub fn self_update() -> Result<Version, String> { +pub fn self_update(options: SelfUpdateOptions) -> Result<Version, String> { if is_homebrew_managed_install() { return Err(format!( "self-update is disabled for Homebrew installs; run `{HOMEBREW_UPDATE_COMMAND}`" @@ -1710,7 +1770,7 @@ pub fn self_update() -> Result<Version, String> { let running_server_plans = plan_running_server_updates(&release)?; let server_update_decisions = - confirm_running_server_update_action(running_server_plans, &release)?; + confirm_running_server_update_action(running_server_plans, &release, options)?; eprintln!("downloading v{}...", release.version); if let Err(e) = @@ -2096,6 +2156,24 @@ mod tests { } #[test] + fn self_update_args_gate_live_handoff() { + assert_eq!( + parse_self_update_args(&[]).unwrap(), + SelfUpdateOptions { + live_handoff: false + } + ); + assert_eq!( + parse_self_update_args(&["--handoff".to_string()]).unwrap(), + SelfUpdateOptions { live_handoff: true } + ); + assert_eq!( + parse_self_update_args(&["--unknown".to_string()]).unwrap_err(), + "unknown update option: --unknown" + ); + } + + #[test] fn parse_live_handoff_before_update_response_defaults_yes_for_blank() { assert_eq!(parse_live_handoff_before_update_response(""), Some(true)); assert_eq!(parse_live_handoff_before_update_response("\n"), Some(true)); @@ -2107,7 +2185,7 @@ mod tests { } #[test] - fn update_requires_live_handoff_when_target_protocol_differs_or_unknown() { + fn update_requires_server_restart_when_target_protocol_differs_or_unknown() { let server = crate::api::RuntimeStatus { version: Some("0.5.5".to_string()), protocol: Some(2), @@ -2128,9 +2206,53 @@ mod tests { ..compatible_release.clone() }; - assert!(!update_requires_live_handoff(&server, &compatible_release)); - assert!(update_requires_live_handoff(&server, &incompatible_release)); - assert!(update_requires_live_handoff(&server, &unknown_release)); + assert!(!update_requires_server_restart( + &server, + &compatible_release + )); + assert!(update_requires_server_restart( + &server, + &incompatible_release + )); + assert!(update_requires_server_restart(&server, &unknown_release)); + } + + #[test] + fn plain_update_requires_restart_for_supported_servers_without_handoff() { + assert!( + !io::stdin().is_terminal(), + "this test relies on noninteractive test stdin" + ); + let release = fake_release("9.8.7", Some(77)); + let plan = RunningServerUpdatePlan { + target: RunningUpdateTarget { + name: Some("work".to_string()), + label: "work".to_string(), + stop_command: "herdr session stop work".to_string(), + attach_command: Some("herdr session attach work".to_string()), + socket_path: crate::session::api_socket_path_for(Some("work")), + client_socket_path: crate::session::client_socket_path_for(Some("work")), + must_be_running: true, + }, + requires_server_restart: true, + server: crate::api::RuntimeStatus { + version: Some("0.6.2".to_string()), + protocol: Some(76), + capabilities: Some(crate::api::schema::ServerCapabilities { live_handoff: true }), + }, + }; + + let err = confirm_running_server_update_action( + vec![plan], + &release, + SelfUpdateOptions { + live_handoff: false, + }, + ) + .unwrap_err(); + + assert!(err.contains("must restart"), "unexpected error: {err}"); + assert!(!err.contains("live handoff"), "unexpected error: {err}"); } #[test] @@ -2289,7 +2411,7 @@ mod tests { } #[test] - fn noninteractive_update_requires_handoff_fails_before_install() { + fn noninteractive_plain_update_requiring_restart_fails_without_handoff() { let _guard = env_lock().lock().unwrap(); assert!( !io::stdin().is_terminal(), @@ -2318,21 +2440,21 @@ mod tests { client_socket_path: crate::session::client_socket_path_for(Some("work")), must_be_running: true, }, - requires_live_handoff: true, + requires_server_restart: true, server, }; - let err = - prompt_to_live_handoff_sessions_before_update(&[&plan], &release, true).unwrap_err(); + let err = confirm_running_server_update_action( + vec![plan], + &release, + SelfUpdateOptions { + live_handoff: false, + }, + ) + .unwrap_err(); - assert!( - err.contains("requires live server handoff"), - "unexpected error: {err}" - ); - assert!( - err.contains("run `herdr update` from an interactive terminal"), - "unexpected error: {err}" - ); + assert!(err.contains("must restart"), "unexpected error: {err}"); + assert!(!err.contains("live handoff"), "unexpected error: {err}"); std::env::remove_var(crate::session::SESSION_ENV_VAR); crate::session::clear_explicit_session_for_test(); } |
