use std::time::Duration;
use iroh::endpoint::presets;
use super::*;
const PATIENCE: Duration = Duration::from_secs(20);
async fn accepting() -> (Endpoint, tokio::sync::mpsc::UnboundedReceiver<Connection>) {
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
let endpoint = Endpoint::builder(presets::N0)
.alpns(vec![transport::ALPN.to_vec()])
.bind()
.await
.expect("an endpoint binds");
let accepting = endpoint.clone();
tokio::spawn(async move {
while let Some(incoming) = accepting.accept().await {
if let Ok(connection) = incoming.await {
let _ = tx.send(connection);
}
}
});
(endpoint, rx)
}
fn ticket_for(endpoint: &Endpoint) -> Ticket {
transport::ticket_from(&endpoint.addr())
}
async fn bound(endpoint: &Endpoint) -> Peer {
tokio::time::timeout(
PATIENCE,
Peer::bind(&ticket_for(endpoint), &ConnectOptions::default()),
)
.await
.expect("the bind must not hang")
.expect("an endpoint binds")
}
async fn connected(endpoint: &Endpoint) -> Peer {
let peer = bound(endpoint).await;
tokio::time::timeout(PATIENCE, peer.redial())
.await
.expect("the dial must not hang")
.expect("a live peer is reachable");
peer
}
#[tokio::test]
async fn binding_reaches_nobody_even_when_the_peer_is_right_there() {
let (endpoint, _accepted) = accepting().await;
let peer = bound(&endpoint).await;
assert!(
peer.current().is_none(),
"binding must not have dialled anyone"
);
}
#[tokio::test]
async fn a_ticket_for_a_live_peer_dials_it_and_holds_the_connection() {
let (endpoint, _accepted) = accepting().await;
let peer = connected(&endpoint).await;
assert!(peer.current().is_some(), "and the connection is held");
}
#[tokio::test]
async fn the_reconnect_loop_makes_the_first_connection_too() {
let (endpoint, _accepted) = accepting().await;
let peer = bound(&endpoint).await;
let lifecycle = Lifecycle::new();
assert_eq!(
lifecycle.status(),
PipeStatus::Idle,
"nothing is reached before the loop runs"
);
tokio::time::timeout(PATIENCE, async {
tokio::select! {
() = keep_connected(&peer, &lifecycle) => {}
() = async {
while lifecycle.status() == PipeStatus::Idle {
tokio::time::sleep(Duration::from_millis(10)).await;
}
} => {}
}
})
.await
.expect("the loop must reach a peer that is right there");
assert!(
matches!(lifecycle.status(), PipeStatus::Direct | PipeStatus::Relayed),
"and report the path it took, not merely stop being idle"
);
assert!(peer.current().is_some(), "with the connection held");
}
#[tokio::test]
async fn a_peer_can_be_dialled_again_at_the_same_identity() {
let (endpoint, _accepted) = accepting().await;
let peer = connected(&endpoint).await;
let first = peer.current().expect("a connection").stable_id();
let path = tokio::time::timeout(PATIENCE, peer.redial())
.await
.expect("the re-dial must not hang")
.expect("the same peer is still there");
let second = peer.current().expect("a connection").stable_id();
assert_ne!(first, second, "a genuinely new connection, not the old one");
assert!(
matches!(path, PeerPath::Direct | PeerPath::Relayed),
"and it reports a path it is actually using"
);
}
#[tokio::test]
async fn forgetting_a_replaced_connection_leaves_its_successor_alone() {
let (endpoint, _accepted) = accepting().await;
let peer = connected(&endpoint).await;
let stale = peer.current().expect("a connection");
tokio::time::timeout(PATIENCE, peer.redial())
.await
.expect("the re-dial must not hang")
.expect("the same peer is still there");
let live = peer.current().expect("a replacement").stable_id();
peer.forget(&stale);
assert_eq!(
peer.current().map(|c| c.stable_id()),
Some(live),
"forgetting the connection that died must not drop the one that replaced it"
);
}
#[tokio::test]
async fn forgetting_the_live_connection_clears_it() {
let (endpoint, _accepted) = accepting().await;
let peer = connected(&endpoint).await;
let live = peer.current().expect("a connection");
peer.forget(&live);
assert!(
peer.current().is_none(),
"and the pipe now knows it has no connection"
);
}
async fn live_connect_side(far: &Endpoint) -> std::sync::Arc<crate::dialer::ConnectState> {
let (state, listener) = tokio::time::timeout(
PATIENCE,
crate::dialer::bind(&ticket_for(far), &ConnectOptions::default()),
)
.await
.expect("the bind must not hang")
.expect("the local port binds");
tokio::spawn(crate::dialer::local_loop(state.clone(), listener));
tokio::time::timeout(PATIENCE, state.peer.redial())
.await
.expect("the dial must not hang")
.expect("a live peer is reachable");
state
}
#[tokio::test]
async fn a_connect_shutdown_closes_the_endpoint_and_not_only_the_connection() {
let (far, _accepted) = accepting().await;
let state = live_connect_side(&far).await;
tokio::time::timeout(PATIENCE, crate::dialer::shutdown(&state))
.await
.expect("the shutdown must not hang");
assert!(
state.peer.endpoint.is_closed(),
"a dropped endpoint is one the peer is never told about"
);
}
#[tokio::test]
async fn a_connect_shutdown_timeout_closes_the_endpoint_too() {
let (far, _accepted) = accepting().await;
let state = live_connect_side(&far).await;
let drained = tokio::time::timeout(PATIENCE, crate::dialer::shutdown_timeout(&state, PATIENCE))
.await
.expect("the shutdown must not hang");
assert!(drained, "nothing was in flight to wait for");
assert!(
state.peer.endpoint.is_closed(),
"the deadline covers the drain, not whether the peer is told at all"
);
}