test: cover graceful-leave instant-evict and rejoin-after-eviction edges
Two reconnect-eviction edges were logically handled but had no explicit assertion: - A graceful ConnEvent::Left must emit PeerLeft *immediately* (not wait on the grace timer). New graceful_leave_evicts_immediately asserts the leave lands well inside the grace window. - After a grace-timer eviction scrubs the peer from seen_connected, a later rejoin's initial dial must be a fresh first-dial, not a reconnect that arms a new eviction. New rejoin_after_grace_eviction_dials_cleanly covers this (distinct from the existing graceful-leave scrub path). Also reorder the eviction task to scrub timers/seen_connected before emitting PeerConnectionFailed, so an observer (or a redial racing the eviction) always sees clean internal state — this also makes the new rejoin-after-eviction test deterministic. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -68,6 +68,24 @@ async fn evicted_within(
|
||||
}
|
||||
}
|
||||
|
||||
/// Drain UI events until a `PeerLeft` for `peer` arrives, or `within` elapses.
|
||||
/// Returns whether the leave fired.
|
||||
async fn left_within(
|
||||
ui_rx: &mut mpsc::Receiver<UiEvent>,
|
||||
peer: EndpointId,
|
||||
within: Duration,
|
||||
) -> bool {
|
||||
let deadline = tokio::time::Instant::now() + within;
|
||||
loop {
|
||||
match tokio::time::timeout_at(deadline, ui_rx.recv()).await {
|
||||
Ok(Some(UiEvent::PeerLeft { id })) if id == peer => return true,
|
||||
Ok(Some(_)) => continue, // ignore PeerConnecting / PeerConnected
|
||||
Ok(None) => return false, // channel closed
|
||||
Err(_) => return false, // timed out — no leave
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const GRACE: Duration = Duration::from_millis(200);
|
||||
|
||||
/// The regression: connect, drop, reconnect, then drop again — the second outage
|
||||
@@ -139,3 +157,48 @@ async fn graceful_leave_lets_a_later_rejoin_dial_cleanly() {
|
||||
"a rejoin's initial dial after a graceful leave must not arm eviction"
|
||||
);
|
||||
}
|
||||
|
||||
/// A graceful leave evicts *immediately* — it emits `PeerLeft` right away rather
|
||||
/// than leaving the peer "reconnecting" until the grace timer (or the slow gossip
|
||||
/// Leave) catches up.
|
||||
#[tokio::test]
|
||||
async fn graceful_leave_evicts_immediately() {
|
||||
let (ui_tx, mut ui_rx) = mpsc::channel(100);
|
||||
let h = make_handler(ui_tx, make_transport().await, GRACE);
|
||||
let peer = fake_peer();
|
||||
|
||||
h.handle(ConnEvent::Connected(peer)).await;
|
||||
h.handle(ConnEvent::Left(peer)).await;
|
||||
|
||||
// Well inside the grace window: the leave must not be gated on the timer.
|
||||
assert!(
|
||||
left_within(&mut ui_rx, peer, GRACE / 2).await,
|
||||
"a graceful leave must emit PeerLeft immediately, not wait for the grace timer"
|
||||
);
|
||||
}
|
||||
|
||||
/// After a peer is evicted by the grace timer, its identity is scrubbed from
|
||||
/// `seen_connected`, so a later rejoin's initial dial is a fresh first-dial and
|
||||
/// must not be mistaken for a reconnect (which would arm a new eviction clock).
|
||||
#[tokio::test]
|
||||
async fn rejoin_after_grace_eviction_dials_cleanly() {
|
||||
let (ui_tx, mut ui_rx) = mpsc::channel(100);
|
||||
let h = make_handler(ui_tx, make_transport().await, GRACE);
|
||||
let peer = fake_peer();
|
||||
|
||||
// Connect, drop, and let the grace timer fire (evicts + scrubs seen_connected).
|
||||
h.handle(ConnEvent::Connected(peer)).await;
|
||||
h.handle(ConnEvent::Connecting(peer)).await;
|
||||
assert!(
|
||||
evicted_within(&mut ui_rx, peer, GRACE * 4).await,
|
||||
"the first outage should evict once the grace window elapses"
|
||||
);
|
||||
|
||||
// A later rejoin's initial dial: scrubbed on eviction, so this is a first-ever
|
||||
// dial again and must not arm a new eviction timer.
|
||||
h.handle(ConnEvent::Connecting(peer)).await;
|
||||
assert!(
|
||||
!evicted_within(&mut ui_rx, peer, GRACE * 3).await,
|
||||
"an initial dial after a grace eviction must not be treated as a reconnect"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user