// Wait strategies for container readiness
// Common functions used by both native and JavaScript backends
//
// Backend-specific implementations:
// - check_tcp_port: See wait_strategy_native.mbt and wait_strategy_js.mbt
// - check_http_endpoint: See wait_strategy_native.mbt and wait_strategy_js.mbt
///|
/// Wait for TCP port to be open with timeout
async fn wait_for_tcp_port(
host : String,
port : Int,
timeout_seconds : Int,
poll_interval_ms? : Int = 500,
) -> Result[Unit, TestContainerError] noraise {
let max_attempts = timeout_seconds * 1000 / poll_interval_ms
for attempt = 0; attempt < max_attempts; attempt = attempt + 1 {
// Check port - ignore errors and treat as not ready
let port_ready = check_tcp_port(host, port) catch { _ => false }
if port_ready {
return Ok(())
}
// Sleep between attempts - ignore sleep errors
@async.sleep(poll_interval_ms) catch {
_ => ()
}
} nobreak {
Err(
WaitStrategyTimeout(
"TCP port \{port} not ready after \{timeout_seconds} seconds",
),
)
}
}
///|
/// Wait for HTTP endpoint to return expected status code with timeout
async fn wait_for_http_endpoint(
host : String,
port : Int,
path : String,
expected_status : Int,
timeout_seconds : Int,
poll_interval_ms? : Int = 500,
) -> Result[Unit, TestContainerError] noraise {
let max_attempts = timeout_seconds * 1000 / poll_interval_ms
for attempt = 0; attempt < max_attempts; attempt = attempt + 1 {
// Check endpoint - ignore errors and treat as not ready
let endpoint_ready = check_http_endpoint(host, port, path, expected_status) catch {
_ => false
}
if endpoint_ready {
return Ok(())
}
// Sleep between attempts - ignore sleep errors
@async.sleep(poll_interval_ms) catch {
_ => ()
}
} nobreak {
Err(
WaitStrategyTimeout(
"HTTP endpoint \{path} did not return status \{expected_status} after \{timeout_seconds} seconds",
),
)
}
}
///|
/// Wait for log message to appear in container logs with timeout
async fn wait_for_log_message(
container : Container,
message : String,
timeout_seconds : Int,
poll_interval_ms? : Int = 500,
) -> Result[Unit, TestContainerError] noraise {
let max_attempts = timeout_seconds * 1000 / poll_interval_ms
for attempt = 0; attempt < max_attempts; attempt = attempt + 1 {
// Get recent logs (last 100 lines to limit memory usage)
let log_options = LogOptions::default().with_tail(100)
// Try to get logs and check for message - treat any error as "not ready yet"
let message_found = {
let logs_result = container.get_logs_with_options(log_options) catch {
_ => Err(DockerCommandFailed("Log retrieval failed"))
}
match logs_result {
Ok(logs) => logs.contains(message)
Err(_) => false
}
}
if message_found {
return Ok(())
}
// Sleep between attempts - ignore sleep errors
@async.sleep(poll_interval_ms) catch {
_ => ()
}
} nobreak {
Err(
WaitStrategyTimeout(
"Log message '\{message}' not found after \{timeout_seconds} seconds",
),
)
}
}
///|
/// Execute wait strategy for container
async fn wait_for_container(
container : Container,
strategy : WaitStrategy,
timeout : Int,
) -> Result[Unit, TestContainerError] noraise {
match strategy {
Seconds(seconds) => {
// Simple delay using async sleep (milliseconds)
let sleep_ms = seconds * 1000 // Convert seconds to milliseconds
@async.sleep(sleep_ms) catch {
e => return Err(DockerCommandFailed("Sleep failed: \{e.to_string()}"))
}
Ok(())
}
TcpPort(container_port) =>
// Get the mapped host port
match container.get_host_port(container_port) {
Some(host_port) =>
// Wait for TCP port to be open
wait_for_tcp_port("localhost", host_port, timeout)
None =>
Err(
InvalidConfiguration(
"Cannot wait for TCP port \{container_port}: port not mapped",
),
)
}
HttpGet(path, expected_status) => {
// For HttpGet, we need to determine which port to use
// If there's only one exposed port, use that
// Otherwise, this is a configuration error
if container.image.exposed_ports.length() == 0 {
return Err(
InvalidConfiguration(
"Cannot wait for HTTP endpoint: no ports exposed",
),
)
}
// Use the first exposed port
let container_port = container.image.exposed_ports[0]
match container.get_host_port(container_port) {
Some(host_port) =>
// Wait for HTTP endpoint to be ready
wait_for_http_endpoint(
"localhost", host_port, path, expected_status, timeout,
)
None =>
Err(
InvalidConfiguration(
"Cannot wait for HTTP endpoint: port \{container_port} not mapped",
),
)
}
}
LogMessage(message) =>
// Wait for log message to appear
wait_for_log_message(container, message, timeout)
}
}