Skip to content

Commit 6e2fe1a

Browse files
committed
Address code review: make wait_for_server async and clean up test
- Convert wait_for_server() to async using tokio::time and tokio::net::TcpStream - Update all callers (start_server, start_server_with_bind, and tests) to async - Clean up self_request_loop test: remove verbose comments and println - Use tracing::debug for test debugging output - Simplify test assertions to focus on essential behavior
1 parent 8eb1fad commit 6e2fe1a

3 files changed

Lines changed: 56 additions & 72 deletions

File tree

tests/common/mod.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -254,15 +254,21 @@ pub fn test_https_allow(use_sudo: bool) {
254254

255255
// Wait until a TCP port on localhost is accepting connections
256256
// Returns true if the port became ready before max_wait elapsed
257-
pub fn wait_for_server(port: u16, max_wait: std::time::Duration) -> bool {
258-
let start = std::time::Instant::now();
257+
pub async fn wait_for_server(port: u16, max_wait: std::time::Duration) -> bool {
258+
let start = tokio::time::Instant::now();
259+
let poll_interval = tokio::time::Duration::from_millis(75);
260+
let settle_time = tokio::time::Duration::from_millis(200);
261+
259262
while start.elapsed() < max_wait {
260-
if std::net::TcpStream::connect(format!("127.0.0.1:{}", port)).is_ok() {
263+
if tokio::net::TcpStream::connect(format!("127.0.0.1:{}", port))
264+
.await
265+
.is_ok()
266+
{
261267
// Give the server a brief moment to finish initialization
262-
std::thread::sleep(std::time::Duration::from_millis(200));
268+
tokio::time::sleep(settle_time).await;
263269
return true;
264270
}
265-
std::thread::sleep(std::time::Duration::from_millis(75));
271+
tokio::time::sleep(poll_interval).await;
266272
}
267273
false
268274
}

tests/self_request_loop.rs

Lines changed: 21 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3,81 +3,57 @@ mod common;
33
use std::net::TcpListener;
44
use std::process::{Command, Stdio};
55
use std::time::Duration;
6+
use tracing::debug;
67

7-
/// Test that requests to the proxy itself are blocked to prevent infinite loops.
8-
/// This reproduces issue #84: https://github.com/coder/httpjail/issues/84
9-
#[test]
10-
fn test_server_mode_self_request_loop_prevention() {
11-
// Logging is auto-initialized via ctor in common::logging
12-
13-
// Find available ports for HTTP and HTTPS
8+
/// Test that requests to the proxy itself are blocked to prevent infinite loops (issue #84)
9+
#[tokio::test]
10+
async fn test_server_mode_self_request_loop_prevention() {
1411
let http_port = find_available_port();
1512
let https_port = find_available_port();
1613

17-
// Start httpjail in server mode
1814
let httpjail_path: &str = env!("CARGO_BIN_EXE_httpjail");
1915
let mut proxy_process = Command::new(httpjail_path)
2016
.env("HTTPJAIL_HTTP_BIND", format!("127.0.0.1:{}", http_port))
2117
.env("HTTPJAIL_HTTPS_BIND", format!("127.0.0.1:{}", https_port))
2218
.arg("--server")
2319
.arg("--js")
24-
.arg("true") // Allow all requests
20+
.arg("true")
2521
.stdin(Stdio::null())
2622
.stdout(Stdio::piped())
2723
.stderr(Stdio::piped())
2824
.spawn()
29-
.expect("Failed to start httpjail in server mode");
25+
.expect("Failed to start httpjail");
3026

31-
// Wait for proxy to start listening on the HTTP port
3227
assert!(
33-
common::wait_for_server(http_port, Duration::from_secs(5)),
28+
common::wait_for_server(http_port, Duration::from_secs(5)).await,
3429
"Server failed to start on port {}",
3530
http_port
3631
);
3732

38-
// Try to make a request to the proxy itself through the proxy
39-
// This should NOT create an infinite loop
40-
let curl_result = Command::new("curl")
33+
let output = Command::new("curl")
4134
.arg("--max-time")
42-
.arg("3") // 3 second timeout
35+
.arg("3")
4336
.arg("--proxy")
4437
.arg(format!("http://127.0.0.1:{}", http_port))
4538
.arg(format!("http://127.0.0.1:{}/test", http_port))
46-
.output();
39+
.output()
40+
.expect("Failed to execute curl");
4741

48-
// Kill the proxy server
4942
proxy_process.kill().ok();
5043
let _ = proxy_process.wait();
5144

52-
match curl_result {
53-
Ok(output) => {
54-
let stdout = String::from_utf8_lossy(&output.stdout);
55-
let stderr = String::from_utf8_lossy(&output.stderr);
56-
let exit_code = output.status.code().unwrap_or(-1);
57-
58-
println!("=== Curl exit code: {}", exit_code);
59-
println!("=== Curl stdout: {}", stdout);
60-
println!("=== Curl stderr: {}", stderr);
61-
62-
// The request should either:
63-
// 1. Be blocked with a 403 (our fix - should contain "blocked by httpjail")
64-
// 2. Fail with connection error (curl timeout/connection refused)
65-
// 3. NOT succeed normally (which would indicate the loop happened but curl timed out)
45+
let stdout = String::from_utf8_lossy(&output.stdout);
46+
let stderr = String::from_utf8_lossy(&output.stderr);
6647

67-
// Without the fix, this creates an infinite loop that consumes resources
68-
// With the fix, we should see "Request blocked by httpjail" in the output
48+
debug!("curl exit code: {}", output.status.code().unwrap_or(-1));
49+
debug!("curl stdout: {}", stdout);
50+
debug!("curl stderr: {}", stderr);
6951

70-
// For now, just verify it doesn't succeed (this test documents the bug)
71-
// After implementing the fix, we'll assert for the specific error message
72-
if output.status.success() && !stdout.contains("Request blocked by httpjail") {
73-
panic!("Request appeared to succeed - this may indicate a loop issue");
74-
}
75-
}
76-
Err(e) => {
77-
// If curl fails to execute, that's a test setup problem
78-
panic!("Failed to execute curl: {}", e);
79-
}
80-
}
52+
assert!(
53+
stdout.contains("Loop detected"),
54+
"Expected loop detection message, got: {}",
55+
stdout
56+
);
8157
}
8258

8359
/// Find an available port for testing

tests/weak_integration.rs

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ fn test_weak_mode_appends_no_proxy() {
173173
}
174174

175175
// Simple server start function - we know the ports we're setting
176-
fn start_server(http_port: u16, https_port: u16) -> Result<std::process::Child, String> {
176+
async fn start_server(http_port: u16, https_port: u16) -> Result<std::process::Child, String> {
177177
let httpjail_path: &str = env!("CARGO_BIN_EXE_httpjail");
178178

179179
let mut cmd = Command::new(httpjail_path);
@@ -192,7 +192,7 @@ fn start_server(http_port: u16, https_port: u16) -> Result<std::process::Child,
192192
.map_err(|e| format!("Failed to start server: {}", e))?;
193193

194194
// Wait for the server to start listening
195-
if !common::wait_for_server(http_port, Duration::from_secs(5)) {
195+
if !common::wait_for_server(http_port, Duration::from_secs(5)).await {
196196
return Err(format!("Server failed to start on port {}", http_port));
197197
}
198198

@@ -250,13 +250,15 @@ fn verify_bind_address(port: u16, expected_ip: &str) -> bool {
250250
std::net::TcpStream::connect(format!("{}:{}", expected_ip, port)).is_ok()
251251
}
252252

253-
#[test]
254-
fn test_server_mode() {
253+
#[tokio::test]
254+
async fn test_server_mode() {
255255
// Test server mode with specific ports
256256
let http_port = 19876;
257257
let https_port = 19877;
258258

259-
let mut server = start_server(http_port, https_port).expect("Failed to start server");
259+
let mut server = start_server(http_port, https_port)
260+
.await
261+
.expect("Failed to start server");
260262

261263
// Test HTTP proxy works
262264
match test_curl_through_proxy(http_port, https_port) {
@@ -278,7 +280,7 @@ fn test_server_mode() {
278280
}
279281

280282
// Helper to start server with custom bind config
281-
fn start_server_with_bind(http_bind: &str, https_bind: &str) -> (std::process::Child, u16) {
283+
async fn start_server_with_bind(http_bind: &str, https_bind: &str) -> (std::process::Child, u16) {
282284
let httpjail_path: &str = env!("CARGO_BIN_EXE_httpjail");
283285

284286
let mut child = Command::new(httpjail_path)
@@ -306,61 +308,61 @@ fn start_server_with_bind(http_bind: &str, https_bind: &str) -> (std::process::C
306308
};
307309

308310
// Wait for server to bind
309-
if !common::wait_for_server(expected_port, Duration::from_secs(3)) {
311+
if !common::wait_for_server(expected_port, Duration::from_secs(3)).await {
310312
child.kill().ok();
311313
panic!("Server failed to bind to port {}", expected_port);
312314
}
313315

314316
(child, expected_port)
315317
}
316318

317-
#[test]
319+
#[tokio::test]
318320
#[serial]
319-
fn test_server_bind_defaults() {
320-
let (mut server, port) = start_server_with_bind("", "");
321+
async fn test_server_bind_defaults() {
322+
let (mut server, port) = start_server_with_bind("", "").await;
321323
assert_eq!(port, 8080, "Server should default to port 8080");
322324
server.kill().ok();
323325
}
324326

325-
#[test]
327+
#[tokio::test]
326328
#[serial]
327-
fn test_server_bind_port_only() {
329+
async fn test_server_bind_port_only() {
328330
// Port-only should bind to all interfaces (0.0.0.0)
329-
let (mut server, port) = start_server_with_bind("19882", "19883");
331+
let (mut server, port) = start_server_with_bind("19882", "19883").await;
330332
assert_eq!(
331333
port, 19882,
332334
"Server should bind to specified port on all interfaces"
333335
);
334336
server.kill().ok();
335337
}
336338

337-
#[test]
339+
#[tokio::test]
338340
#[serial]
339-
fn test_server_bind_colon_prefix_port() {
341+
async fn test_server_bind_colon_prefix_port() {
340342
// :port (Go-style) should bind to all interfaces (0.0.0.0)
341-
let (mut server, port) = start_server_with_bind(":19892", ":19893");
343+
let (mut server, port) = start_server_with_bind(":19892", ":19893").await;
342344
assert_eq!(
343345
port, 19892,
344346
"Server should bind to specified port on all interfaces with :port format"
345347
);
346348
server.kill().ok();
347349
}
348350

349-
#[test]
351+
#[tokio::test]
350352
#[serial]
351-
fn test_server_bind_all_interfaces() {
352-
let (mut server, port) = start_server_with_bind("0.0.0.0:19884", "0.0.0.0:19885");
353+
async fn test_server_bind_all_interfaces() {
354+
let (mut server, port) = start_server_with_bind("0.0.0.0:19884", "0.0.0.0:19885").await;
353355
assert_eq!(
354356
port, 19884,
355357
"Server should bind to specified port on 0.0.0.0"
356358
);
357359
server.kill().ok();
358360
}
359361

360-
#[test]
362+
#[tokio::test]
361363
#[serial]
362-
fn test_server_bind_ip_without_port() {
363-
let (mut server, port) = start_server_with_bind("127.0.0.1", "127.0.0.1");
364+
async fn test_server_bind_ip_without_port() {
365+
let (mut server, port) = start_server_with_bind("127.0.0.1", "127.0.0.1").await;
364366
assert_eq!(
365367
port, 8080,
366368
"Server should use default port 8080 when only IP specified"

0 commit comments

Comments
 (0)