test(e2e): avoid console port 9001 clash and fail fast on dead server (#4603)

This commit is contained in:
Zhengchao An
2026-07-09 12:07:15 +08:00
committed by GitHub
parent 073628e8be
commit 79d6de860c
4 changed files with 81 additions and 1 deletions
@@ -62,6 +62,9 @@ mod tests {
let binary_path = rustfs_binary_path();
let mut command = Command::new(&binary_path);
command.env("RUST_LOG", "rustfs=info,rustfs_notify=debug");
// Keep the embedded console off its fixed default port :9001, which may
// already be taken by unrelated local services (e.g. Docker Desktop).
command.env("RUSTFS_CONSOLE_ENABLE", "false");
for (key, value) in extra_env {
command.env(key, value);
}
+14 -1
View File
@@ -391,6 +391,10 @@ impl RustFSTestEnvironment {
let binary_path = rustfs_binary_path();
let mut command = Command::new(&binary_path);
command.env("RUST_LOG", "rustfs=info,rustfs_notify=debug");
// The embedded console would bind the fixed default port :9001, which
// collides with unrelated local services (e.g. Docker Desktop). Tests
// that need the console can still override this via extra_env.
command.env("RUSTFS_CONSOLE_ENABLE", "false");
for (key, value) in extra_env {
command.env(key, value);
}
@@ -435,11 +439,20 @@ impl RustFSTestEnvironment {
/// connections before the S3 stack is fully initialized, which causes
/// early requests to fail intermittently. Treat readiness as "S3 API
/// responds successfully" instead.
pub async fn wait_for_server_ready(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
///
/// Fails immediately if the spawned server process has already exited
/// (e.g. a port bind failure) instead of polling out the full timeout.
pub async fn wait_for_server_ready(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
info!("Waiting for RustFS server to be ready on {}", self.address);
let client = self.create_s3_client();
for i in 0..60 {
if let Some(process) = self.process.as_mut()
&& let Ok(Some(status)) = process.try_wait()
{
return Err(format!("RustFS server process exited before becoming ready: {status}").into());
}
if TcpStream::connect(&self.address).await.is_ok() && client.list_buckets().send().await.is_ok() {
info!("✅ RustFS server is ready after {} attempts", i + 1);
return Ok(());
+4
View File
@@ -46,6 +46,10 @@ mod list_objects_duplicates_test;
#[cfg(test)]
mod quota_test;
// Harness regression tests: console port isolation + fail-fast startup
#[cfg(test)]
mod server_startup_failfast_test;
#[cfg(test)]
mod bucket_policy_check_test;
@@ -0,0 +1,60 @@
// Copyright 2024 RustFS Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Regression tests for e2e harness server-startup behavior.
//!
//! The harness spawns servers with the embedded console disabled so they never
//! contend for its fixed default port :9001 (often held by unrelated local
//! services such as Docker Desktop), and `wait_for_server_ready` must report a
//! server that dies during startup immediately instead of polling out the full
//! readiness timeout.
#[cfg(test)]
mod tests {
use crate::common::{RustFSTestEnvironment, init_logging, rustfs_binary_path};
use serial_test::serial;
use std::net::TcpListener;
use std::time::{Duration, Instant};
/// Force the console back on (extra_env overrides the harness default)
/// while :9001 is occupied: the server exits at startup, and the harness
/// must surface that promptly rather than waiting out the 60s timeout.
#[tokio::test]
#[serial]
async fn test_start_fails_fast_when_server_exits_during_startup() {
init_logging();
// Occupy :9001 on both stacks; a failed bind means another local
// process already holds the port, which serves equally well.
let _guard_v6 = TcpListener::bind(("::", 9001)).ok();
let _guard_v4 = TcpListener::bind(("0.0.0.0", 9001)).ok();
// Resolve (and possibly build) the binary before starting the timer.
let _ = rustfs_binary_path();
let mut env = RustFSTestEnvironment::new().await.expect("Failed to create test environment");
let started = Instant::now();
let result = env
.start_rustfs_server_with_env(vec![], &[("RUSTFS_CONSOLE_ENABLE", "true")])
.await;
let elapsed = started.elapsed();
let err = result.expect_err("server start should fail while :9001 is occupied");
assert!(err.to_string().contains("exited before becoming ready"), "unexpected start error: {err}");
assert!(
elapsed < Duration::from_secs(30),
"startup failure should be detected fast, took {elapsed:?}"
);
}
}