Skip to content

Commit 578ba04

Browse files
committed
[bfops/wasm-test]: wait_for_all funcs
1 parent 0ef6f35 commit 578ba04

1 file changed

Lines changed: 88 additions & 4 deletions

File tree

  • sdks/rust/tests/test-counter/src

sdks/rust/tests/test-counter/src/lib.rs

Lines changed: 88 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
#![allow(clippy::disallowed_macros)]
22

33
use spacetimedb_data_structures::map::{HashMap, HashSet};
4-
use std::{
5-
sync::{Arc, Condvar, Mutex},
6-
time::Duration,
7-
};
4+
use std::sync::{Arc, Condvar, Mutex};
5+
#[cfg(not(target_arch = "wasm32"))]
6+
use std::time::Duration;
87

98
const TEST_TIMEOUT_SECS: u64 = 5 * 60;
109

@@ -55,7 +54,31 @@ impl TestCounter {
5554
})
5655
}
5756

57+
// Keep this legacy sync API for existing native-first callers.
58+
// wasm callers should prefer `wait_for_all_async` so we do not
59+
// block the JS event loop while waiting for callbacks.
5860
pub fn wait_for_all(&self) {
61+
#[cfg(target_arch = "wasm32")]
62+
futures::executor::block_on(self.wait_for_all_wasm_async());
63+
64+
#[cfg(not(target_arch = "wasm32"))]
65+
self.wait_for_all_native();
66+
}
67+
68+
pub async fn wait_for_all_async(&self) {
69+
#[cfg(target_arch = "wasm32")]
70+
{
71+
// wasm/web test clients run callbacks on a single-threaded event loop,
72+
// so waiting must be async to allow callback tasks to make progress.
73+
self.wait_for_all_wasm_async().await;
74+
}
75+
76+
#[cfg(not(target_arch = "wasm32"))]
77+
self.wait_for_all_native();
78+
}
79+
80+
#[cfg(not(target_arch = "wasm32"))]
81+
fn wait_for_all_native(&self) {
5982
let lock = self.inner.lock().expect("TestCounterInner Mutex is poisoned");
6083
let (lock, timeout_result) = self
6184
.wait_until_done
@@ -100,4 +123,65 @@ impl TestCounter {
100123
}
101124
}
102125
}
126+
127+
#[cfg(target_arch = "wasm32")]
128+
async fn wait_for_all_wasm_async(&self) {
129+
use gloo_timers::future::TimeoutFuture;
130+
131+
const WAIT_INTERVAL_MS: u32 = 10;
132+
const MAX_WAIT_ITERATIONS: u32 = (TEST_TIMEOUT_SECS as u32 * 1000) / WAIT_INTERVAL_MS;
133+
134+
// On wasm/web the SDK message loop runs on the same single-threaded event loop
135+
// as test code. Blocking on a Condvar here can deadlock callbacks forever.
136+
// We poll with timer yields so websocket/callback tasks can continue to run.
137+
let all_tests_finished = || {
138+
let inner = self.inner.lock().expect("TestCounterInner Mutex is poisoned");
139+
inner.outcomes.len() == inner.registered.len()
140+
};
141+
142+
for _ in 0..MAX_WAIT_ITERATIONS {
143+
if all_tests_finished() {
144+
return;
145+
}
146+
TimeoutFuture::new(WAIT_INTERVAL_MS).await;
147+
}
148+
149+
let lock = self.inner.lock().expect("TestCounterInner Mutex is poisoned");
150+
if lock.outcomes.len() != lock.registered.len() {
151+
let mut timeout_count = 0;
152+
let mut failed_count = 0;
153+
for test in lock.registered.iter() {
154+
match lock.outcomes.get(test) {
155+
None => {
156+
timeout_count += 1;
157+
println!("TIMEOUT: {test}");
158+
}
159+
Some(Err(e)) => {
160+
failed_count += 1;
161+
println!("FAILED: {test}:\n\t{e:?}\n");
162+
}
163+
Some(Ok(())) => {
164+
println!("PASSED: {test}");
165+
}
166+
}
167+
}
168+
panic!("{timeout_count} tests timed out and {failed_count} tests failed");
169+
} else {
170+
let mut failed_count = 0;
171+
for (test, outcome) in lock.outcomes.iter() {
172+
match outcome {
173+
Ok(()) => println!("PASSED: {test}"),
174+
Err(e) => {
175+
failed_count += 1;
176+
println!("FAILED: {test}:\n\t{e:?}\n");
177+
}
178+
}
179+
}
180+
if failed_count != 0 {
181+
panic!("{failed_count} tests failed");
182+
} else {
183+
println!("All tests passed");
184+
}
185+
}
186+
}
103187
}

0 commit comments

Comments
 (0)