Packages
testcontainers
1.10.3
2.3.1
2.3.0
2.2.0
2.1.0
2.1.0-rc1
2.0.0
2.0.0-rc3
2.0.0-rc2
2.0.0-rc
1.14.1
1.14.0
1.13.5
1.13.4
1.13.3
1.13.2
1.13.1
1.13.0
1.12.0
1.11.8
1.11.7
1.11.6
1.11.5
1.11.4
1.11.3
1.11.2
1.11.1
1.11.0
1.10.5
1.10.4
1.10.3
1.10.2
1.10.1
1.10.0
1.9.0
1.8.4
1.8.3
1.8.2
1.8.1
1.8.0
1.7.0
1.6.0
1.5.1
1.5.0
1.4.2
1.4.1
1.4.0
1.3.1
1.3.0
1.2.11
1.2.10
1.2.9
1.2.8
1.2.7
1.2.6
1.2.5
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.1.3
1.1.2
1.1.1
1.1.0
1.0.1
1.0.0
1.0.0-beta.1
1.0.0-beta
0.9.3
0.9.2
0.9.1
0.9.0
Testcontainers is an Elixir library that supports ExUnit tests, providing lightweight, throwaway instances of common databases, Selenium web browsers, or anything else that can run in a Docker container.
Current section
Files
Jump to
Current section
Files
lib/wait_strategy/command_wait_strategy.ex
# SPDX-License-Identifier: MIT
# Original by: Marco Dallagiacoma @ 2023 in https://github.com/dallagi/excontainers
# Modified by: Jarl André Hübenthal @ 2023
defmodule Testcontainers.CommandWaitStrategy do
@moduledoc """
Considers a container ready as soon as a command runs successfully inside it.
"""
@retry_delay 200
defstruct [:command, :timeout, retry_delay: @retry_delay]
# Public interface
@doc """
Creates a new CommandWaitStrategy.
This strategy waits until the given command executes successfully inside the container.
"""
def new(command, timeout \\ 5000, retry_delay \\ @retry_delay) do
%__MODULE__{command: command, timeout: timeout, retry_delay: retry_delay}
end
# Private functions and implementations
defimpl Testcontainers.WaitStrategy do
alias Testcontainers.{Docker, Logger}
@impl true
def wait_until_container_is_ready(wait_strategy, container, conn) do
started_at = get_current_time_millis()
perform_recursive_wait(wait_strategy, container.container_id, conn, started_at)
end
# Main loop for waiting strategy
defp perform_recursive_wait(wait_strategy, container_id, conn, started_at) do
with {:ok, 0} <- execute_command_and_wait(wait_strategy, container_id, conn) do
:ok
else
{:ok, exit_code} ->
handle_non_zero_exit(wait_strategy, container_id, exit_code, conn, started_at)
error ->
handle_execution_error(error, wait_strategy)
end
end
defp execute_command_and_wait(
%{command: command, timeout: timeout, retry_delay: retry_delay},
container_id,
conn
) do
with {:ok, exec_id} <- Docker.Api.start_exec(container_id, command, conn) do
started_at = get_current_time_millis()
wait_for_command_completion(exec_id, timeout, started_at, retry_delay, conn)
end
end
defp handle_non_zero_exit(wait_strategy, container_id, exit_code, conn, started_at) do
if timed_out?(started_at, wait_strategy.timeout) do
{:error, strategy_timed_out(wait_strategy.timeout, started_at), wait_strategy}
else
log_retry_message(container_id, exit_code, wait_strategy.retry_delay)
:timer.sleep(wait_strategy.retry_delay)
perform_recursive_wait(wait_strategy, container_id, conn, started_at)
end
end
defp handle_execution_error({:error, reason}, wait_strategy),
do: {:error, reason, wait_strategy}
defp wait_for_command_completion(exec_id, timeout, started_at, retry_delay, conn) do
case Docker.Api.inspect_exec(exec_id, conn) do
{:ok, %{running: true}} ->
wait_unless_timeout(exec_id, timeout, started_at, retry_delay, conn)
{:ok, exec_status} ->
{:ok, exec_status.exit_code}
end
end
defp wait_unless_timeout(exec_id, timeout, started_at, retry_delay, conn) do
if timed_out?(started_at, timeout) do
{:error, strategy_timed_out(timeout, started_at)}
else
:timer.sleep(retry_delay)
wait_for_command_completion(exec_id, timeout, started_at, retry_delay, conn)
end
end
defp get_current_time_millis(), do: System.monotonic_time(:millisecond)
defp timed_out?(started_at, timeout), do: get_current_time_millis() - started_at > timeout
defp strategy_timed_out(timeout, started_at) do
{:command_wait_strategy, :timeout, timeout,
elapsed_time: get_current_time_millis() - started_at}
end
defp log_retry_message(container_id, exit_code, delay) do
Logger.log(
"Command execution in container #{container_id} failed with exit_code #{exit_code}, retrying in #{delay}ms."
)
end
end
end