From 25f22231a657aaef363da2620b6d655b87a7540c Mon Sep 17 00:00:00 2001 From: funman300 Date: Tue, 19 May 2026 11:31:38 -0700 Subject: [PATCH] fix(test): make leaderboard opt-in/opt-out tests robust under parallel runner (closes #5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The four tests polled the async task pool with a fixed budget of five app.update() calls. Under cargo test --workspace the pool's background threads are starved by other tests, so even an instantly-resolving future can take more than five frames to be polled. Replace the fixed loop with a deadline-bounded loop (5 s timeout) that exits early once the expected side-effect is observable — the same pattern used in sync_plugin.rs tests. Co-Authored-By: Claude Sonnet 4.6 --- solitaire_engine/src/leaderboard_plugin.rs | 63 ++++++++++++++++++++-- 1 file changed, 58 insertions(+), 5 deletions(-) diff --git a/solitaire_engine/src/leaderboard_plugin.rs b/solitaire_engine/src/leaderboard_plugin.rs index 7a09bcc..2d7628a 100644 --- a/solitaire_engine/src/leaderboard_plugin.rs +++ b/solitaire_engine/src/leaderboard_plugin.rs @@ -1159,9 +1159,23 @@ mod tests { .spawn(async { Err::<(), String>("network error".to_string()) }); app.world_mut().resource_mut::().0 = Some(failed_task); - // Allow the task to complete and be polled. - for _ in 0..5 { + // Pump until the task is polled or a deadline elapses. A fixed + // update count is unreliable under parallel `cargo test --workspace` + // load — the AsyncComputeTaskPool background threads can be starved + // long enough that 5 updates finish before the task completes. + // Mirrors the deadline-loop pattern used in sync_plugin tests. + let deadline = std::time::Instant::now() + std::time::Duration::from_secs(5); + loop { app.update(); + let msgs = app.world().resource::>(); + let mut cursor = msgs.get_cursor(); + if cursor.read(msgs).next().is_some() { + break; + } + if std::time::Instant::now() >= deadline { + break; + } + std::thread::yield_now(); } let msgs = app.world().resource::>(); @@ -1183,8 +1197,19 @@ mod tests { .spawn(async { Err::<(), String>("network error".to_string()) }); app.world_mut().resource_mut::().0 = Some(failed_task); - for _ in 0..5 { + // Deadline-bounded pump — see opt_in_error_fires_warning_toast for rationale. + let deadline = std::time::Instant::now() + std::time::Duration::from_secs(5); + loop { app.update(); + let msgs = app.world().resource::>(); + let mut cursor = msgs.get_cursor(); + if cursor.read(msgs).next().is_some() { + break; + } + if std::time::Instant::now() >= deadline { + break; + } + std::thread::yield_now(); } let msgs = app.world().resource::>(); @@ -1210,8 +1235,22 @@ mod tests { let ok_task = AsyncComputeTaskPool::get().spawn(async { Ok::<(), String>(()) }); app.world_mut().resource_mut::().0 = Some(ok_task); - for _ in 0..5 { + // Deadline-bounded pump — see opt_in_error_fires_warning_toast for rationale. + let deadline = std::time::Instant::now() + std::time::Duration::from_secs(5); + loop { app.update(); + if app + .world() + .resource::() + .0 + .leaderboard_opted_in + { + break; + } + if std::time::Instant::now() >= deadline { + break; + } + std::thread::yield_now(); } assert!( @@ -1237,8 +1276,22 @@ mod tests { let ok_task = AsyncComputeTaskPool::get().spawn(async { Ok::<(), String>(()) }); app.world_mut().resource_mut::().0 = Some(ok_task); - for _ in 0..5 { + // Deadline-bounded pump — see opt_in_error_fires_warning_toast for rationale. + let deadline = std::time::Instant::now() + std::time::Duration::from_secs(5); + loop { app.update(); + if !app + .world() + .resource::() + .0 + .leaderboard_opted_in + { + break; + } + if std::time::Instant::now() >= deadline { + break; + } + std::thread::yield_now(); } assert!(