1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
//! Shared async readiness helpers for beam e2e tests.
//!
//! # The Flakiness Pattern
//!
//! BEAM tests traditionally used `tokio::time::sleep(N)` as a "ready"
//! signal. This is wrong on two axes:
//!
//! 1. **Time is not readiness.** A 1500ms sleep might be too short on
//! cold process starts (first 1-2 runs of a session) when the
//! tokio runtime is still warming up the actor scheduler. It
//! might also be far longer than needed on warm runs, wasting
//! test time.
//!
//! 2. **The substrate exposes real readiness signals.** Every
//! network adapter in `src/adapters/` has observable state we
//! can poll on:
//! - `WsServer::peer_count()` — completed WebSocket handshakes
//! - `OutgoingWebsocketManager::connected_count()` — successful
//! `connect_async` results
//! - TCP port bound (verifiable via `TcpStream::connect`)
//!
//! # The Pattern
//!
//! Every `sleep(N)` in a test should be replaced with a `wait_for_X(...)`
//! helper that polls on the actual invariant. Each helper accepts a
//! timeout — when the timeout elapses, the helper panics with a clear
//! message so the failure mode is diagnosable.
//!
//! # Why panic instead of returning `Result`
//!
//! Tests should fail loudly at the point of the readiness violation.
//! Returning `Result` would force every caller to `.await?` or
//! `.expect(...)` it. A panic with context is more diagnostic and
//! keeps test bodies readable.
//!
//! # Substrate Truths (verified 2026-07-25)
//!
//! - `WsServer::peer_count()` — non-blocking read of the `clients`
//! `RwLock`. Returns 0 if the lock is held (rare, retry handles it).
//! - `OutgoingWebsocketManager::connected_count()` — reads
//! `self.clients.len()`. Only increments after `connect_async`
//! succeeds AND the `WsConn` actor has been spawned.
//! - TCP port bound — `TcpStream::connect` succeeds when the kernel
//! accept queue has room. Does NOT guarantee the user-space
//! handshake completed — pair with `wait_for_peer_count` for that.
use ;
use ;
use TcpStream;
use sleep;
/// Default poll interval. 50ms is fast enough to keep total test time
/// close to the time the invariant actually takes to settle, while not
/// wasting CPU on tight loops.
pub const POLL_INTERVAL_MS: u64 = 50;
/// Poll a TCP port until it accepts connections or the timeout elapses.
///
/// Eliminates blind-sleep races against the actor's `pre_start`.
/// Verifies only that the kernel listen socket has been bound — does
/// NOT verify the WebSocket handshake. For that, pair with
/// [`wait_for_peer_count`] or [`wait_for_connected_count`].
///
/// # Panics
/// If the port is not accepting connections within `timeout_ms` ms.
pub async
/// Poll the [`WsServer`] until `expected_peers` have completed the
/// WebSocket handshake and registered as connected clients.
///
/// # Why this is correct
///
/// `WsServer::peer_count()` increments when a
/// `beam::adapters::ws_conn::WsConn` actor finishes the WS upgrade
/// and registers its address. That happens AFTER the TCP listener
/// accepts AND the WS handshake completes — the same condition the
/// broadcast needs to succeed.
///
/// # Panics
/// If the expected peer count is not reached within `timeout_ms` ms.
pub async
/// Poll the [`OutgoingWebsocketManager`] until `expected_urls` remote
/// URLs have an active WebSocket connection.
///
/// Mirrors [`wait_for_peer_count`] from the client side. The two
/// together form the readiness invariant for any test that crosses
/// a WebSocket mesh boundary: server side sees N peers AND client
/// side has N connected clients.
///
/// # Panics
/// If the expected connected count is not reached within `timeout_ms` ms.
pub async