Skip to content

Commit 195f96e

Browse files
committed
fix(test): fix integration test failures and port cleanup issue
Signed-off-by: Xin Liu <[email protected]>
1 parent 97e7b66 commit 195f96e

File tree

1 file changed

+146
-35
lines changed

1 file changed

+146
-35
lines changed

tests/integration.rs

Lines changed: 146 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@
22
// Tests HTTP/HTTPS and WebSocket proxy functionality
33

44
use std::process::{Child, Command};
5-
use std::sync::OnceLock;
65
use std::sync::{Mutex, Once};
76
use tokio::time::Duration;
87

98
static INIT: Once = Once::new();
10-
static SERVER_PROCESS: OnceLock<Mutex<Option<Child>>> = OnceLock::new();
9+
static SERVER_PROCESS: Mutex<Option<Child>> = Mutex::new(None);
1110

1211
/// Get test server port from environment variable or use default 8080
1312
fn get_test_port() -> u16 {
@@ -27,6 +26,33 @@ fn get_ws_base_url() -> String {
2726
format!("ws://localhost:{}", get_test_port())
2827
}
2928

29+
/// Cleanup function to kill the test server
30+
fn cleanup_test_server() {
31+
println!("🧹 Cleaning up test server...");
32+
if let Ok(mut guard) = SERVER_PROCESS.lock() {
33+
if let Some(mut child) = guard.take() {
34+
let pid = child.id();
35+
println!("🛑 Killing server process (PID: {})", pid);
36+
37+
// On Unix, kill the entire process group
38+
#[cfg(unix)]
39+
{
40+
use std::process::Command as SysCommand;
41+
// Try to kill the process group first
42+
let _ = SysCommand::new("kill")
43+
.args(&["-TERM", &format!("-{}", pid)])
44+
.status();
45+
std::thread::sleep(std::time::Duration::from_millis(500));
46+
}
47+
48+
// Then kill the process itself
49+
let _ = child.kill();
50+
let _ = child.wait();
51+
println!("✅ Server process stopped");
52+
}
53+
}
54+
}
55+
3056
/// Initialize test environment (start server, wait for it to be ready)
3157
fn setup_test_server() {
3258
INIT.call_once(|| {
@@ -54,16 +80,22 @@ fn setup_test_server() {
5480
println!("🔧 Starting server on port {}", port);
5581

5682
// Build server command with environment variables
83+
// Use a process group so we can kill all children later
5784
let mut server_cmd = Command::new("./target/release/ss-proxy");
5885
server_cmd
5986
.args(&["--port", &port.to_string(), "--log-level", "debug"])
6087
.env("TEST_PORT", port.to_string()); // Ensure child process knows the port
6188

89+
// On Unix, set process group ID to enable killing the entire group
90+
#[cfg(unix)]
91+
{
92+
use std::os::unix::process::CommandExt;
93+
server_cmd.process_group(0);
94+
}
95+
6296
let server = server_cmd.spawn().expect("Failed to start server");
6397

64-
SERVER_PROCESS
65-
.set(Mutex::new(Some(server)))
66-
.expect("Failed to set server process");
98+
*SERVER_PROCESS.lock().unwrap() = Some(server);
6799

68100
// Wait for server to start
69101
println!("⏳ Waiting for server to start...");
@@ -72,17 +104,36 @@ fn setup_test_server() {
72104
});
73105
}
74106

75-
/// Cleanup function (called when tests complete)
76-
/// Note: The server will run for all tests and will be cleaned up when the test process exits
77-
impl Drop for ServerGuard {
107+
/// Cleanup function to kill the server when all tests are done
108+
pub struct TestCleanup;
109+
110+
impl Drop for TestCleanup {
78111
fn drop(&mut self) {
79-
// Don't kill the server on each test - it should persist across all tests
80-
// The server process will be cleaned up when the test process exits
112+
// This will be called when CLEANUP_GUARD is dropped
113+
// In practice, this may not be called by the test runner
114+
// Use cleanup_test_server() explicitly or run ./cleanup_test_server.sh
115+
cleanup_test_server();
81116
}
82117
}
83118

119+
// Global cleanup guard - attempt to cleanup when binary exits
120+
// NOTE: Rust's test runner may not call this reliably
121+
// If tests leave the server running, manually run: ./cleanup_test_server.sh <PORT>
122+
#[allow(dead_code)]
123+
static CLEANUP_GUARD: TestCleanup = TestCleanup;
124+
125+
// Alternative: implement a custom test macro or add cleanup to each test
126+
// For now, users should run cleanup_test_server.sh after tests if needed
127+
84128
struct ServerGuard;
85129

130+
impl Drop for ServerGuard {
131+
fn drop(&mut self) {
132+
// Don't kill the server on each test - it should persist across all tests
133+
// Cleanup will happen via the global CLEANUP_GUARD when all tests finish
134+
}
135+
}
136+
86137
// =============================================================================
87138
// HTTP/HTTPS Proxy Tests
88139
// =============================================================================
@@ -152,7 +203,11 @@ async fn test_http_proxy_with_query_params() {
152203
setup_test_server();
153204
let _guard = ServerGuard;
154205

155-
let client = reqwest::Client::new();
206+
let client = reqwest::Client::builder()
207+
.timeout(Duration::from_secs(60)) // Increase timeout to 60 seconds
208+
.build()
209+
.expect("Failed to build client");
210+
156211
let response = client
157212
.get(format!(
158213
"{}/test-http/get?foo=bar&hello=world",
@@ -239,7 +294,10 @@ async fn test_websocket_echo() {
239294
let (mut write, mut read) = ws_stream.split();
240295

241296
// Skip the initial greeting message from echo.websocket.org
242-
let _ = tokio::time::timeout(Duration::from_secs(2), read.next()).await;
297+
// Read and discard the greeting message (e.g., "Request served by ...")
298+
if let Ok(Some(Ok(msg))) = tokio::time::timeout(Duration::from_secs(3), read.next()).await {
299+
println!("Skipped greeting message: {:?}", msg);
300+
}
243301

244302
// Send a text message
245303
let test_message = "Hello WebSocket!";
@@ -248,19 +306,35 @@ async fn test_websocket_echo() {
248306
.await
249307
.expect("Failed to send message");
250308

251-
// Receive the echo
252-
let received = tokio::time::timeout(Duration::from_secs(5), read.next())
253-
.await
254-
.expect("Timeout waiting for message")
255-
.expect("No message received")
256-
.expect("Error receiving message");
257-
258-
match received {
259-
Message::Text(text) => {
260-
assert_eq!(text.to_string(), test_message);
309+
// Receive the echo - may need to skip additional greeting messages
310+
let mut received_echo = None;
311+
for _ in 0..3 {
312+
let received = tokio::time::timeout(Duration::from_secs(5), read.next())
313+
.await
314+
.expect("Timeout waiting for message")
315+
.expect("No message received")
316+
.expect("Error receiving message");
317+
318+
match received {
319+
Message::Text(text) => {
320+
let text_str = text.to_string();
321+
// Skip greeting messages from echo.websocket.org
322+
if text_str.starts_with("Request served by") {
323+
println!("Skipping server info: {}", text_str);
324+
continue;
325+
}
326+
// This is our echo
327+
received_echo = Some(text_str);
328+
break;
329+
}
330+
_ => panic!("Expected text message, got: {:?}", received),
261331
}
262-
_ => panic!("Expected text message, got: {:?}", received),
263332
}
333+
334+
assert_eq!(
335+
received_echo.expect("Did not receive echo message"),
336+
test_message
337+
);
264338
}
265339

266340
#[tokio::test]
@@ -279,7 +353,10 @@ async fn test_websocket_binary_message() {
279353
let (mut write, mut read) = ws_stream.split();
280354

281355
// Skip the initial greeting message from echo.websocket.org
282-
let _ = tokio::time::timeout(Duration::from_secs(2), read.next()).await;
356+
// Read and discard the greeting message (e.g., "Request served by ...")
357+
if let Ok(Some(Ok(msg))) = tokio::time::timeout(Duration::from_secs(3), read.next()).await {
358+
println!("Skipped greeting message: {:?}", msg);
359+
}
283360

284361
// Send binary data
285362
let test_data = vec![1u8, 2, 3, 4, 5];
@@ -288,19 +365,38 @@ async fn test_websocket_binary_message() {
288365
.await
289366
.expect("Failed to send binary message");
290367

291-
// Receive the echo
292-
let received = tokio::time::timeout(Duration::from_secs(5), read.next())
293-
.await
294-
.expect("Timeout waiting for message")
295-
.expect("No message received")
296-
.expect("Error receiving message");
297-
298-
match received {
299-
Message::Binary(data) => {
300-
assert_eq!(data.to_vec(), test_data);
368+
// Receive the echo - may need to skip additional text messages
369+
let mut received_binary = None;
370+
for _ in 0..3 {
371+
let received = tokio::time::timeout(Duration::from_secs(5), read.next())
372+
.await
373+
.expect("Timeout waiting for message")
374+
.expect("No message received")
375+
.expect("Error receiving message");
376+
377+
match received {
378+
Message::Binary(data) => {
379+
// This is our echo
380+
received_binary = Some(data.to_vec());
381+
break;
382+
}
383+
Message::Text(text) => {
384+
let text_str = text.to_string();
385+
// Skip greeting messages from echo.websocket.org
386+
if text_str.starts_with("Request served by") {
387+
println!("Skipping server info: {}", text_str);
388+
continue;
389+
}
390+
panic!("Expected binary message, got unexpected text: {}", text_str);
391+
}
392+
_ => panic!("Expected binary message, got: {:?}", received),
301393
}
302-
_ => panic!("Expected binary message, got: {:?}", received),
303394
}
395+
396+
assert_eq!(
397+
received_binary.expect("Did not receive binary echo"),
398+
test_data
399+
);
304400
}
305401

306402
#[tokio::test]
@@ -369,3 +465,18 @@ async fn test_websocket_multiple_messages() {
369465
}
370466
}
371467
}
468+
469+
// =============================================================================
470+
// Cleanup Test - Runs last to kill the server
471+
// =============================================================================
472+
473+
/// This test should ideally run last to clean up the server
474+
/// Note: Test execution order is not guaranteed in Rust
475+
/// If server is still running after tests, use: ./cleanup_test_server.sh <PORT>
476+
#[tokio::test]
477+
async fn test_zzz_cleanup() {
478+
// Use zzz_ prefix to encourage this test to run last (though not guaranteed)
479+
println!("🧹 Running cleanup test...");
480+
cleanup_test_server();
481+
println!("✅ Cleanup test complete");
482+
}

0 commit comments

Comments
 (0)