#[cfg(feature = "async")]
#[cfg(test)]
mod tests {
use ftr::Ftr;
#[cfg(target_os = "macos")]
use ftr::TracerouteConfig;
#[tokio::test]
async fn test_async_shows_multiple_hops() {
let target = "8.8.8.8";
let ftr_instance = Ftr::new();
let result = ftr_instance.trace(target).await;
if let Err(e) = &result {
if e.to_string().contains("Permission denied")
|| e.to_string().contains("requires root")
{
eprintln!("Skipping test due to permission error: {}", e);
return;
}
}
let result = result.expect("trace to 8.8.8.8 failed with unexpected error");
eprintln!("Async trace completed:");
eprintln!(" Protocol: {:?}", result.protocol_used);
eprintln!(" Socket mode: {:?}", result.socket_mode_used);
eprintln!(" Total hops: {}", result.hop_count());
let hops_with_responses: Vec<_> = result
.hops
.iter()
.filter(|hop| hop.addr.is_some())
.collect();
eprintln!(" Hops with responses: {}", hops_with_responses.len());
if hops_with_responses.is_empty() {
eprintln!("\n=== DEBUG: Got 0 responses ===");
eprintln!("Environment info:");
eprintln!(" OS: {}", std::env::consts::OS);
eprintln!(" CI: {:?}", std::env::var("CI"));
eprintln!(" GITHUB_ACTIONS: {:?}", std::env::var("GITHUB_ACTIONS"));
if std::env::var("GITHUB_ACTIONS").is_ok() {
let os = std::env::consts::OS;
if os == "windows" || os == "linux" {
eprintln!("\nNo ICMP responses on GitHub Actions {} runner", os);
eprintln!("This is expected - GitHub Actions runners have restricted ICMP.");
eprintln!("Windows: Azure blocks inbound ICMP packets.");
eprintln!("Linux: Unreliable ICMP due to virtualization/network restrictions.");
eprintln!("Skipping test on GitHub Actions {}.", os);
return;
}
}
eprintln!("\nUnexpected: No ICMP responses received");
eprintln!("This could indicate:");
eprintln!(" - Firewall blocking ICMP");
eprintln!(" - Network configuration issues");
eprintln!(" - Target unreachable");
}
let unique_addresses: std::collections::HashSet<_> =
result.hops.iter().filter_map(|hop| hop.addr).collect();
assert!(
result.hop_count() >= 3,
"Expected at least 3 hops, got {}",
result.hop_count()
);
assert!(
!unique_addresses.is_empty(),
"Expected responses from at least 1 address, got {}",
unique_addresses.len()
);
}
#[tokio::test]
#[cfg(target_os = "macos")]
async fn test_macos_async_icmp_works() {
let config = TracerouteConfig::builder()
.target("1.1.1.1")
.protocol(ftr::ProbeProtocol::Icmp)
.build()
.expect("failed to build traceroute config");
let ftr_instance = Ftr::new();
let result = ftr_instance.trace_with_config(config).await;
if let Err(e) = &result {
if e.to_string().contains("Permission denied") {
eprintln!("Skipping macOS ICMP test due to permissions");
return;
}
}
let result = result.expect("macOS async ICMP trace failed");
assert!(
result.hop_count() >= 3,
"macOS async ICMP should show multiple hops, got {}",
result.hop_count()
);
let hops_with_responses = result.hops.iter().filter(|hop| hop.addr.is_some()).count();
assert!(
hops_with_responses >= 2,
"macOS async ICMP should show responses from multiple hops, got {}",
hops_with_responses
);
}
#[tokio::test]
async fn test_parallel_probing_to_external_target() {
let target = "1.1.1.1";
let ftr_instance = Ftr::new();
let result = ftr_instance.trace(target).await;
if let Err(e) = &result {
if e.to_string().contains("Permission denied") {
eprintln!("Skipping external target test due to permissions");
return;
}
if e.to_string().contains("timed out") || e.to_string().contains("unreachable") {
eprintln!("Skipping external target test due to network issues");
return;
}
}
let result = result.expect("external trace failed");
assert!(
result.hop_count() >= 2,
"External target should have multiple hops, got {}",
result.hop_count()
);
let mut prev_ttl = 0;
for hop in &result.hops {
assert!(
hop.ttl > prev_ttl,
"TTLs should be in ascending order: {} <= {}",
hop.ttl,
prev_ttl
);
prev_ttl = hop.ttl;
}
let ttls: Vec<u8> = result.hops.iter().map(|h| h.ttl).collect();
for window in ttls.windows(2) {
assert!(
window[1] - window[0] <= 5,
"Large gap in TTL sequence: {} to {}",
window[0],
window[1]
);
}
}
}