fix(cli): fail continuous runs on peer closure
Keep PeerClosed as a distinct successful library outcome while treating it as an error at the CLI and TUI boundary for uninterrupted continuous runs. Track aggregate peer-close outcomes independently of bounded recent history and cover finite, continuous, grouped, mixed-failure, interruption, and TUI status behavior.
This commit is contained in:
7 files changed
+358
-19
No files matched your search
@@ -14,7 +14,7 @@ use irtt_client::{
|
||||
|
||||
use crate::{
|
||||
cmd::tui::args::{ResolvedTuiTarget, TuiArgs},
|
||||
shared::client::{is_shutdown_requested, ClientSession},
|
||||
shared::client::{is_shutdown_requested, session::peer_close_run_error, ClientSession},
|
||||
};
|
||||
|
||||
use super::ui::{should_render, TuiConfig, TuiState, TuiStatus, TuiTerminal};
|
||||
@@ -129,6 +129,16 @@ pub fn run_tui(
|
||||
|
||||
let events = session.close()?;
|
||||
state.process_events(&events);
|
||||
interrupted |= is_shutdown_requested(shutdown_requested);
|
||||
if let Some(error) = peer_close_run_error(
|
||||
continuous,
|
||||
interrupted,
|
||||
if session.is_peer_closed() { 1 } else { 0 },
|
||||
) {
|
||||
state.set_run_error(error.clone());
|
||||
render_if_due(&mut terminal, &state, &mut next_render, true)?;
|
||||
return Err(error.into());
|
||||
}
|
||||
state.set_status(TuiStatus::Complete);
|
||||
render_if_due(&mut terminal, &state, &mut next_render, true)?;
|
||||
Ok(())
|
||||
@@ -249,6 +259,7 @@ fn run_group_tui(
|
||||
}
|
||||
}
|
||||
|
||||
interrupted |= is_shutdown_requested(shutdown_requested);
|
||||
if outcome.end_reason == ManagedGroupEndReason::Cancelled && !interrupted {
|
||||
return match exit {
|
||||
GroupLoopExit::IdleGraceElapsed => {
|
||||
@@ -262,6 +273,15 @@ fn run_group_tui(
|
||||
}
|
||||
};
|
||||
}
|
||||
if let Some(error) = peer_close_run_error(
|
||||
args.is_continuous(),
|
||||
interrupted,
|
||||
outcome.peer_closed_target_outcomes,
|
||||
) {
|
||||
state.set_run_error(error.clone());
|
||||
render_if_due(terminal, state, next_render, true)?;
|
||||
return Err(error.into());
|
||||
}
|
||||
let successful_targets = outcome.successful_target_outcomes;
|
||||
let failed_targets = outcome.failed_target_outcomes;
|
||||
if !interrupted && successful_targets == 0 && failed_targets > 0 {
|
||||
|
||||
@@ -409,6 +409,12 @@ impl TuiState {
|
||||
self.push_event(format!("error {message}"));
|
||||
}
|
||||
|
||||
pub(super) fn set_run_error(&mut self, message: String) {
|
||||
self.status = TuiStatus::Error;
|
||||
self.last_warning = Some(message.clone());
|
||||
self.push_event(format!("error {message}"));
|
||||
}
|
||||
|
||||
pub(super) fn clear_visible_history(&mut self) {
|
||||
for target in &mut self.targets {
|
||||
target.graph_history.clear();
|
||||
@@ -2412,6 +2418,23 @@ mod tests {
|
||||
.is_some_and(|warning| warning.contains("pending probe limit")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_error_preserves_peer_closed_target_status() {
|
||||
let mut state = TuiState::with_target_labels(TuiConfig::default(), ["a".to_owned()]);
|
||||
state.process_target_outcome(&target_outcome("a", ManagedTargetEndReason::PeerClosed));
|
||||
|
||||
state.set_run_error(
|
||||
"continuous run ended because of peer closure (1 target session)".to_owned(),
|
||||
);
|
||||
|
||||
assert_eq!(state.status, TuiStatus::Error);
|
||||
assert_eq!(state.targets[0].status, TargetStatus::Closed);
|
||||
assert!(state
|
||||
.last_warning
|
||||
.as_deref()
|
||||
.is_some_and(|warning| warning.contains("peer closure")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_failed_terminal_outcomes_set_error_status() {
|
||||
let mut state =
|
||||
|
||||
Reference in new issue
Block a user