From 550d2dd5dc38713e5554db9aecdf021aa41210a2 Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Mon, 21 Sep 2026 08:31:12 +0000 Subject: [PATCH] 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) --- tests/common/nip09_helpers.rs | 19 +++++++++---------- tests/fixture_lifecycle.rs | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/tests/common/nip09_helpers.rs b/tests/common/nip09_helpers.rs index 3a48e84..060d2c2 100644 --- a/tests/common/nip09_helpers.rs +++ b/tests/common/nip09_helpers.rs @@ -37,17 +37,16 @@ where F: FnMut() -> Fut, Fut: std::future::Future, { - let deadline = tokio::time::Instant::now() + timeout; - loop { - if condition().await { - return; + tokio::time::timeout(timeout, async { + loop { + if condition().await { + return; + } + tokio::time::sleep(Duration::from_millis(50)).await; } - assert!( - tokio::time::Instant::now() < deadline, - "{description} did not hold within {timeout:?}" - ); - 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 diff --git a/tests/fixture_lifecycle.rs b/tests/fixture_lifecycle.rs index 266ca1e..7cc6940 100644 --- a/tests/fixture_lifecycle.rs +++ b/tests/fixture_lifecycle.rs @@ -56,3 +56,18 @@ async fn flapping_relay_closes_connections_on_stop() { async fn setup_drop_relay_closes_connections_on_stop() { 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::() + }), + ) + .await + .expect("wait helper failed to enforce its own deadline"); +}