Skip to content

Commit 6553498

Browse files
test: add regression tests for current_thread runtime compatibility
Add integration tests to prevent regression of issue #1, where measure_many would hang with single-threaded Tokio runtimes. Tests: - ping_localhost_current_thread: Core regression test for issue #1 - ping_localhost_multi_thread: Baseline test for comparison - ping_sequential_current_thread: Multiple sequential ping operations Also adds rt-multi-thread to dev-dependencies for the baseline test.
1 parent c4430a7 commit 6553498

2 files changed

Lines changed: 116 additions & 1 deletion

File tree

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ tokio = { version = "1.25", features = ["net", "sync", "rt", "time"] }
2424
futures-core = { version = "0.3", optional = true }
2525

2626
[dev-dependencies]
27-
tokio = { version = "1.25", features = ["macros"] }
27+
tokio = { version = "1.25", features = ["macros", "rt-multi-thread"] }
2828
futures-util = { version = "0.3", default-features = false }
2929

3030
[features]

tests/runtime_compatibility.rs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
//! Regression test for GitHub issue #1:
2+
//! `measure_many` hangs with `current_thread` tokio runtime.
3+
//!
4+
//! The bug was a race condition where the background receive task would
5+
//! block on socket recv() before processing subscription messages. In a
6+
//! single-threaded runtime, this caused ICMP replies to be dropped because
7+
//! subscribers weren't registered yet.
8+
//!
9+
//! This test requires network access and the ability to send ICMP packets
10+
//! to localhost.
11+
12+
use std::{net::IpAddr, time::Duration};
13+
14+
use futures_util::StreamExt;
15+
use massping::DualstackPinger;
16+
use tokio::time;
17+
18+
/// Test that pinging localhost works with `current_thread` runtime.
19+
///
20+
/// This is a regression test for issue #1 where `measure_many` would hang
21+
/// indefinitely on single-threaded runtimes due to a race condition between
22+
/// subscription registration and ICMP reply processing.
23+
#[tokio::test(flavor = "current_thread")]
24+
async fn ping_localhost_current_thread() {
25+
let localhost: IpAddr = "127.0.0.1".parse().unwrap();
26+
27+
let pinger = DualstackPinger::new().expect("failed to create pinger");
28+
let mut stream = pinger.measure_many([localhost].into_iter());
29+
30+
// With the bug, this would hang forever. With the fix, localhost should
31+
// respond within milliseconds. We use a generous 5 second timeout to
32+
// account for slow CI environments.
33+
let result = time::timeout(Duration::from_secs(5), stream.next()).await;
34+
35+
match result {
36+
Ok(Some((addr, rtt))) => {
37+
assert_eq!(addr, localhost);
38+
// Localhost RTT should be very fast (sub-millisecond typically)
39+
assert!(rtt < Duration::from_secs(1), "RTT too high: {rtt:?}");
40+
}
41+
Ok(None) => {
42+
panic!("stream ended unexpectedly");
43+
}
44+
Err(_) => {
45+
panic!(
46+
"timeout waiting for ping response - \
47+
this indicates the current_thread runtime bug (issue #1) has regressed"
48+
);
49+
}
50+
}
51+
}
52+
53+
/// Test that pinging localhost works with `multi_thread` runtime.
54+
///
55+
/// This serves as a baseline - if this test passes but `current_thread` fails,
56+
/// it confirms the issue is specific to single-threaded runtimes.
57+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
58+
async fn ping_localhost_multi_thread() {
59+
let localhost: IpAddr = "127.0.0.1".parse().unwrap();
60+
61+
let pinger = DualstackPinger::new().expect("failed to create pinger");
62+
let mut stream = pinger.measure_many([localhost].into_iter());
63+
64+
let result = time::timeout(Duration::from_secs(5), stream.next()).await;
65+
66+
match result {
67+
Ok(Some((addr, rtt))) => {
68+
assert_eq!(addr, localhost);
69+
assert!(rtt < Duration::from_secs(1), "RTT too high: {rtt:?}");
70+
}
71+
Ok(None) => {
72+
panic!("stream ended unexpectedly");
73+
}
74+
Err(_) => {
75+
panic!("timeout waiting for ping response");
76+
}
77+
}
78+
}
79+
80+
/// Test pinging multiple times sequentially with `current_thread` runtime.
81+
///
82+
/// This tests that multiple sequential ping operations work correctly,
83+
/// ensuring the fix handles repeated use of the pinger.
84+
#[tokio::test(flavor = "current_thread")]
85+
async fn ping_sequential_current_thread() {
86+
let localhost: IpAddr = "127.0.0.1".parse().unwrap();
87+
88+
let pinger = DualstackPinger::new().expect("failed to create pinger");
89+
90+
// Perform multiple sequential pings
91+
for i in 0..3 {
92+
let mut stream = pinger.measure_many([localhost].into_iter());
93+
94+
let result = time::timeout(Duration::from_secs(5), stream.next()).await;
95+
96+
match result {
97+
Ok(Some((addr, rtt))) => {
98+
assert_eq!(addr, localhost);
99+
assert!(
100+
rtt < Duration::from_secs(1),
101+
"RTT too high on ping {i}: {rtt:?}"
102+
);
103+
}
104+
Ok(None) => {
105+
panic!("stream ended unexpectedly on ping {i}");
106+
}
107+
Err(_) => {
108+
panic!(
109+
"timeout on ping {i} - \
110+
current_thread runtime bug may have regressed"
111+
);
112+
}
113+
}
114+
}
115+
}

0 commit comments

Comments
 (0)