Skip to content

Commit eba89e8

Browse files
committed
chore(tests): optimize test server
Signed-off-by: Xin Liu <[email protected]>
1 parent 4636723 commit eba89e8

File tree

2 files changed

+122
-57
lines changed

2 files changed

+122
-57
lines changed

.github/workflows/test.yml

Lines changed: 64 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ on:
1717
- '**/*.rs'
1818
- '**/*.hurl'
1919
- 'run_tests.sh'
20+
- 'tests/**'
2021
pull_request:
2122
branches: [ main ]
2223

@@ -87,44 +88,87 @@ jobs:
8788
8889
- name: Start WebSocket echo server
8990
run: |
90-
# Use robust Python WebSocket echo server
91+
# Use ultra-robust Python WebSocket echo server with manual message loop
9192
cat > ws-echo.py << 'EOF'
9293
import asyncio
9394
import websockets
9495
import logging
96+
import sys
9597
96-
logging.basicConfig(level=logging.INFO)
98+
logging.basicConfig(
99+
level=logging.INFO,
100+
format='%(asctime)s - %(levelname)s - %(message)s',
101+
stream=sys.stdout
102+
)
97103
logger = logging.getLogger(__name__)
98104
99105
async def echo(websocket, path):
100-
logger.info(f"New WebSocket connection from {websocket.remote_address}")
106+
client_addr = websocket.remote_address
107+
logger.info(f"✅ New connection from {client_addr} to {path}")
108+
109+
message_count = 0
101110
try:
102-
async for message in websocket:
103-
# Echo back the message
104-
await websocket.send(message)
105-
logger.info(f"Echoed message: {len(message)} bytes")
106-
except websockets.exceptions.ConnectionClosed:
107-
logger.info("Connection closed normally")
111+
# Manual receive loop with explicit error checking
112+
while True:
113+
try:
114+
# Wait for message with timeout
115+
message = await asyncio.wait_for(
116+
websocket.recv(),
117+
timeout=60.0 # 60 second timeout
118+
)
119+
120+
message_count += 1
121+
msg_type = "binary" if isinstance(message, bytes) else "text"
122+
msg_size = len(message)
123+
124+
logger.info(f"📨 Received {msg_type} message #{message_count}: {msg_size} bytes from {client_addr}")
125+
126+
# Echo back immediately
127+
await websocket.send(message)
128+
logger.info(f"📤 Echoed {msg_type} message #{message_count}: {msg_size} bytes to {client_addr}")
129+
130+
except asyncio.TimeoutError:
131+
logger.warning(f"⏰ Timeout waiting for message from {client_addr}")
132+
continue
133+
except websockets.exceptions.ConnectionClosedOK:
134+
logger.info(f"✅ Connection closed normally by {client_addr} after {message_count} messages")
135+
break
136+
except websockets.exceptions.ConnectionClosedError as e:
137+
logger.warning(f"⚠️ Connection closed with error from {client_addr}: {e}")
138+
break
139+
108140
except Exception as e:
109-
logger.error(f"Error in echo handler: {e}")
141+
logger.error(f"❌ Unexpected error handling {client_addr}: {e}", exc_info=True)
110142
finally:
111-
logger.info("Connection ended")
143+
logger.info(f"🔚 Handler ended for {client_addr} (processed {message_count} messages)")
112144
113145
async def main():
114-
# Configure server with proper settings
146+
host = "0.0.0.0"
147+
port = 8890
148+
149+
logger.info(f"🚀 Starting WebSocket echo server on {host}:{port}")
150+
151+
# Start server with very permissive settings
115152
async with websockets.serve(
116153
echo,
117-
"0.0.0.0",
118-
8890,
119-
ping_interval=20,
120-
ping_timeout=20,
121-
close_timeout=10
154+
host,
155+
port,
156+
ping_interval=None, # Disable ping/pong (let client handle it)
157+
ping_timeout=None, # No ping timeout
158+
close_timeout=10, # 10 seconds for close handshake
159+
max_size=10 * 1024 * 1024, # 10MB max message
160+
max_queue=32, # Max 32 queued messages
161+
compression=None # Disable compression for simplicity
122162
):
123-
logger.info("WebSocket echo server started on port 8890")
124-
await asyncio.Future()
163+
logger.info(f"✅ WebSocket echo server is ready and listening")
164+
await asyncio.Future() # Run forever
125165
126166
if __name__ == "__main__":
127-
asyncio.run(main())
167+
try:
168+
asyncio.run(main())
169+
except KeyboardInterrupt:
170+
logger.info("🛑 Server stopped by user")
171+
sys.exit(0)
128172
EOF
129173
130174
docker run -d \

tests/mock-data/ws-echo.py

Lines changed: 58 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
#!/usr/bin/env python3
22
"""
3-
Robust WebSocket Echo Server for Testing
3+
Ultra-Robust WebSocket Echo Server for Testing
44
5-
This server echoes back any messages it receives.
6-
It properly handles WebSocket control frames and connection lifecycle.
5+
This server echoes back any messages it receives using a manual receive loop
6+
instead of async for, which can exit unexpectedly in some scenarios.
77
"""
88

99
import asyncio
@@ -14,44 +14,67 @@
1414

1515
logging.basicConfig(
1616
level=logging.INFO,
17-
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
17+
format="%(asctime)s - %(levelname)s - %(message)s",
18+
stream=sys.stdout,
1819
)
1920
logger = logging.getLogger(__name__)
2021

2122

2223
async def echo(websocket, path):
2324
"""
24-
Echo handler that receives messages and sends them back.
25+
Echo handler that receives messages and sends them back using manual loop.
2526
2627
Args:
2728
websocket: The WebSocket connection
2829
path: The request path
2930
"""
3031
client_addr = websocket.remote_address
31-
logger.info(f"New WebSocket connection from {client_addr} (path: {path})")
32+
logger.info(f"New connection from {client_addr} to {path}")
3233

34+
message_count = 0
3335
try:
34-
async for message in websocket:
35-
# Determine message type
36-
if isinstance(message, bytes):
37-
msg_type = "binary"
38-
msg_size = len(message)
39-
else:
40-
msg_type = "text"
36+
# Manual receive loop with explicit error checking
37+
while True:
38+
try:
39+
# Wait for message with timeout
40+
message = await asyncio.wait_for(
41+
websocket.recv(), timeout=60.0 # 60 second timeout
42+
)
43+
44+
message_count += 1
45+
msg_type = "binary" if isinstance(message, bytes) else "text"
4146
msg_size = len(message)
4247

43-
# Echo back the message
44-
await websocket.send(message)
45-
logger.info(f"Echoed {msg_type} message: {msg_size} bytes from {client_addr}")
48+
logger.info(
49+
f"📨 Received {msg_type} message #{message_count}: {msg_size} bytes from {client_addr}"
50+
)
51+
52+
# Echo back immediately
53+
await websocket.send(message)
54+
logger.info(
55+
f"📤 Echoed {msg_type} message #{message_count}: {msg_size} bytes to {client_addr}"
56+
)
57+
58+
except asyncio.TimeoutError:
59+
logger.warning(f"⏰ Timeout waiting for message from {client_addr}")
60+
continue
61+
except websockets.exceptions.ConnectionClosedOK:
62+
logger.info(
63+
f"✅ Connection closed normally by {client_addr} after {message_count} messages"
64+
)
65+
break
66+
except websockets.exceptions.ConnectionClosedError as e:
67+
logger.warning(
68+
f"⚠️ Connection closed with error from {client_addr}: {e}"
69+
)
70+
break
4671

47-
except websockets.exceptions.ConnectionClosedOK:
48-
logger.info(f"Connection closed normally by {client_addr}")
49-
except websockets.exceptions.ConnectionClosedError as e:
50-
logger.warning(f"Connection closed with error from {client_addr}: {e}")
5172
except Exception as e:
52-
logger.error(f"Unexpected error in echo handler from {client_addr}: {e}", exc_info=True)
73+
logger.error(f"Unexpected error handling {client_addr}: {e}", exc_info=True)
5374
finally:
54-
logger.info(f"Connection ended with {client_addr}")
75+
logger.info(
76+
f"🔚 Handler ended for {client_addr} (processed {message_count} messages)"
77+
)
5578

5679

5780
async def main():
@@ -61,38 +84,36 @@ async def main():
6184
host = "0.0.0.0"
6285
port = 8890
6386

64-
# Configure server with proper timeouts and settings
87+
logger.info(f"🚀 Starting WebSocket echo server on {host}:{port}")
88+
89+
# Start server with very permissive settings
6590
server = await websockets.serve(
6691
echo,
6792
host,
6893
port,
69-
# Ping settings to keep connection alive
70-
ping_interval=20, # Send ping every 20 seconds
71-
ping_timeout=20, # Wait 20 seconds for pong response
72-
close_timeout=10, # Wait 10 seconds for close handshake
73-
# Max message size (10MB)
74-
max_size=10 * 1024 * 1024,
75-
# Max queue size for incoming messages
76-
max_queue=32,
94+
ping_interval=None, # Disable ping/pong (let client handle it)
95+
ping_timeout=None, # No ping timeout
96+
close_timeout=10, # 10 seconds for close handshake
97+
max_size=10 * 1024 * 1024, # 10MB max message
98+
max_queue=32, # Max 32 queued messages
99+
compression=None, # Disable compression for simplicity
77100
)
78101

79-
logger.info(f"WebSocket echo server started on {host}:{port}")
80-
logger.info("Press Ctrl+C to stop")
102+
logger.info(f"✅ WebSocket echo server is ready and listening")
81103

82104
# Keep the server running
83105
try:
84106
await asyncio.Future()
85107
except KeyboardInterrupt:
86-
logger.info("Shutting down server...")
108+
logger.info("🛑 Shutting down server...")
87109
server.close()
88110
await server.wait_closed()
89-
logger.info("Server stopped")
111+
logger.info("Server stopped")
90112

91113

92114
if __name__ == "__main__":
93115
try:
94116
asyncio.run(main())
95117
except KeyboardInterrupt:
96-
logger.info("Server interrupted by user")
97-
sys.exit(0)
118+
logger.info("🛑 Server interrupted by user")
98119
sys.exit(0)

0 commit comments

Comments
 (0)