test(lifecycle): bound each observable wait including its query

The merged wait helper checked elapsed time only after awaiting its
condition, so a stuck query could defeat the advertised deadline.
Apply Tokio's timeout to the whole polling future while keeping the
existing condition, polling interval and diagnostic message.

Add a paused-time regression with a permanently pending condition and
an outer guard. It fails if the helper lacks its own deadline, without
requiring wall-clock sleeps or hanging the test runner. Production
relay behavior and existing test assertions are unchanged.

Validation: the targeted regression, formatting and all-target Clippy
pass. Three complete workspace runs follow this commit.

Assisted-by: Codex (GPT-6)
This commit is contained in:
DanConwayDev
2026-09-21 08:31:12 +00:00
parent f2f5647f87
commit 550d2dd5dc
2 changed files with 24 additions and 10 deletions
+4 -5
View File
@@ -37,17 +37,16 @@ where
F: FnMut() -> Fut, F: FnMut() -> Fut,
Fut: std::future::Future<Output = bool>, Fut: std::future::Future<Output = bool>,
{ {
let deadline = tokio::time::Instant::now() + timeout; tokio::time::timeout(timeout, async {
loop { loop {
if condition().await { if condition().await {
return; return;
} }
assert!(
tokio::time::Instant::now() < deadline,
"{description} did not hold within {timeout:?}"
);
tokio::time::sleep(Duration::from_millis(50)).await; tokio::time::sleep(Duration::from_millis(50)).await;
} }
})
.await
.unwrap_or_else(|_| panic!("{description} did not hold within {timeout:?}"));
} }
/// Wait until the relay serves `event_id`, bounded by /// Wait until the relay serves `event_id`, bounded by
+15
View File
@@ -56,3 +56,18 @@ async fn flapping_relay_closes_connections_on_stop() {
async fn setup_drop_relay_closes_connections_on_stop() { async fn setup_drop_relay_closes_connections_on_stop() {
check_shutdown!(SetupDropRelay::start().await); check_shutdown!(SetupDropRelay::start().await);
} }
#[tokio::test(start_paused = true)]
#[should_panic(expected = "stalled query did not hold within")]
async fn observable_wait_deadline_cancels_a_stalled_query() {
// The outer timer makes a missing inner deadline fail deterministically
// rather than hanging this regression. Paused time avoids a wall-clock wait.
tokio::time::timeout(
Duration::from_secs(20),
common::wait_for("stalled query", Duration::from_secs(10), || {
std::future::pending::<bool>()
}),
)
.await
.expect("wait helper failed to enforce its own deadline");
}