use std::net::SocketAddr;
use std::str::FromStr as _;
use std::sync::{Arc, Mutex};
use iroh::endpoint::presets;
use iroh::{Endpoint, RelayUrl};
use super::*;
use crate::transport;
const PATIENCE: Duration = Duration::from_secs(20);
#[test]
fn the_relay_is_what_makes_a_path_relayed_and_everything_else_is_direct() {
let relay = TransportAddr::Relay(
RelayUrl::from_str("https://relay.example.com/").expect("a relay URL"),
);
let direct = TransportAddr::Ip(SocketAddr::from_str("192.0.2.7:41641").expect("an address"));
assert_eq!(classify(&relay), PeerPath::Relayed);
assert_eq!(classify(&direct), PeerPath::Direct);
}
#[test]
fn a_path_that_is_not_established_yet_reads_as_relayed_and_unmeasured() {
assert_eq!(Reading::PENDING.path, PeerPath::Relayed);
assert_eq!(
Reading::PENDING.rtt,
None,
"there is nothing to measure yet"
);
}
#[test]
fn a_round_trip_time_is_reported_in_whole_milliseconds() {
assert_eq!(millis(Duration::from_micros(23_400)), 23);
assert_eq!(millis(Duration::ZERO), 0);
assert_eq!(
millis(Duration::MAX),
u64::MAX,
"saturating, never wrapping"
);
}
fn direct() -> Reading {
Reading {
path: PeerPath::Direct,
rtt: Some(Duration::from_millis(3)),
}
}
fn relayed() -> Reading {
Reading {
path: PeerPath::Relayed,
rtt: Some(Duration::from_millis(87)),
}
}
#[test]
fn a_reading_taken_between_paths_reports_the_path_that_was_in_force() {
assert_eq!(
settled(Reading::PENDING, direct()),
direct(),
"a lapse is not a trip to the relay"
);
assert_eq!(
settled(relayed(), direct()),
relayed(),
"and a real move to the relay still is one"
);
assert_eq!(settled(direct(), relayed()), direct(), "in both directions");
assert_eq!(
settled(Reading::PENDING, Reading::PENDING),
Reading::PENDING,
"with nothing ever established there is nothing to carry forward"
);
}
#[tokio::test(start_paused = true)]
async fn a_watcher_that_reads_a_lapse_goes_on_reporting_the_path_it_had() {
let mut script = [direct(), Reading::PENDING, Reading::PENDING, direct()].into_iter();
let mut seen: Vec<Reading> = Vec::new();
let _ = tokio::time::timeout(
CADENCE * 3 + CADENCE / 2,
repeat(
|| script.next().unwrap_or_else(direct),
|reading| seen.push(reading),
),
)
.await;
assert!(
seen.len() >= 4,
"the whole script must have been read, not the first entry alone: {seen:?}"
);
assert!(
seen.iter().all(|r| *r == direct()),
"a lapse must publish the reading in force, not `Relayed`: {seen:?}"
);
}
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)
}
async fn connected_to(far: &Endpoint) -> (Endpoint, Connection) {
let near = Endpoint::builder(presets::N0)
.bind()
.await
.expect("an endpoint binds");
let connection = tokio::time::timeout(PATIENCE, near.connect(far.addr(), transport::ALPN))
.await
.expect("the dial must not hang")
.expect("a live peer is reachable");
(near, connection)
}
#[tokio::test]
async fn a_live_connection_reads_as_the_path_it_is_on_with_a_measured_cost() {
let (far, _accepted) = accepting().await;
let (_near, connection) = connected_to(&far).await;
let reading = read(&connection);
assert_eq!(
reading.path,
PeerPath::Direct,
"two endpoints on one machine do not need a relay"
);
assert!(
reading.rtt.is_some(),
"a selected path has a round-trip estimate"
);
}
#[tokio::test]
async fn a_live_connection_is_read_again_rather_than_sampled_once() {
let (far, _accepted) = accepting().await;
let (_near, connection) = connected_to(&far).await;
let lifecycle = Lifecycle::new();
let seen: Arc<Mutex<Vec<Reading>>> = Arc::new(Mutex::new(Vec::new()));
let recording = seen.clone();
let _ = tokio::time::timeout(
CADENCE * 3 + CADENCE / 2,
follow(&connection, &lifecycle, move |reading| {
recording
.lock()
.expect("nothing panics holding it")
.push(reading);
}),
)
.await;
let readings = seen.lock().expect("nothing panics holding it").clone();
assert!(
readings.len() >= 3,
"three cadences must produce at least three readings, not one: {readings:?}"
);
assert!(
readings.iter().all(|r| r.path == PeerPath::Direct),
"and each one is a real read of the live connection: {readings:?}"
);
}
#[tokio::test]
async fn closing_the_pipe_ends_the_watcher() {
let (far, _accepted) = accepting().await;
let (_near, connection) = connected_to(&far).await;
let lifecycle = Lifecycle::new();
lifecycle.close(crate::status::CloseReason::Shutdown);
tokio::time::timeout(PATIENCE, follow(&connection, &lifecycle, |_| {}))
.await
.expect("a closed pipe must not leave a watcher reading forever");
}