diff --git a/Makefile b/Makefile index 57181f8..7dc2851 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ release: cargo build --release test: - cargo test --workspace --features protobuf,grpc + cargo test --workspace --features protobuf,grpc -- --test-threads=1 fmt: cargo fmt --all diff --git a/tests/integration/src/helpers.rs b/tests/integration/src/helpers.rs index c203254..2e1a091 100644 --- a/tests/integration/src/helpers.rs +++ b/tests/integration/src/helpers.rs @@ -155,13 +155,13 @@ impl TestServer { panic!("failed to spawn ember-server at {}: {e}", binary.display()) }); - // wait for the server to accept TCP connections + // wait until the server is ready to handle commands (not just TCP open) let deadline = std::time::Instant::now() + Duration::from_secs(5); loop { if std::time::Instant::now() > deadline { panic!("ember-server failed to start within 5 seconds on port {port}"); } - if std::net::TcpStream::connect(format!("127.0.0.1:{port}")).is_ok() { + if Self::ping_ready_sync(port) { break; } std::thread::sleep(Duration::from_millis(50)); @@ -220,6 +220,31 @@ impl TestServer { } } + /// Sends PING and returns true if the server responds with PONG. + /// Used as a readiness probe — stronger than a bare TCP connect check + /// because it confirms the command dispatcher is running. + fn ping_ready_sync(port: u16) -> bool { + use std::io::{Read, Write}; + + let Ok(mut stream) = std::net::TcpStream::connect(format!("127.0.0.1:{port}")) else { + return false; + }; + stream + .set_read_timeout(Some(Duration::from_millis(500))) + .ok(); + if stream.write_all(b"*1\r\n$4\r\nPING\r\n").is_err() { + return false; + } + let mut buf = vec![0u8; 128]; + match stream.read(&mut buf) { + Ok(n) if n > 0 => { + let r = &buf[..n]; + r.starts_with(b"+PONG") || r.starts_with(b"$4\r\nPONG") + } + _ => false, + } + } + /// Connects a test client to this server. pub async fn connect(&self) -> TestClient { TestClient::connect(self.port).await