Current section

Files

Jump to
testcontainers lib wait_strategy port_wait_strategy.ex
Raw

lib/wait_strategy/port_wait_strategy.ex

# SPDX-License-Identifier: MIT
defmodule Testcontainers.WaitStrategy.PortWaitStrategy do
@moduledoc """
Considers container as ready as soon as a command runs successfully inside the container.
"""
defstruct [:ip, :port, :timeout]
@doc """
Creates a new CommandWaitStrategy to wait until the given command executes successfully inside the container.
"""
def new(ip, port, timeout \\ 5000),
do: %__MODULE__{ip: ip, port: port, timeout: timeout}
end
defimpl Testcontainers.WaitStrategy, for: Testcontainers.WaitStrategy.PortWaitStrategy do
alias Testcontainers.Docker
alias Testcontainers.Container
@impl true
def wait_until_container_is_ready(wait_strategy, id_or_name) do
with {:ok, %Container{} = container} <- Docker.Api.get_container(id_or_name) do
host_port = Container.mapped_port(container, wait_strategy.port)
case wait_for_port(wait_strategy.ip, host_port, wait_strategy.timeout) do
{:ok, :port_is_open} ->
:ok
_ ->
:timer.sleep(100)
wait_until_container_is_ready(wait_strategy, id_or_name)
end
end
end
def wait_for_port(ip, port, timeout \\ 1000)
when is_binary(ip) and is_integer(port) and is_integer(timeout) do
wait_for_port(ip, port, timeout, :os.system_time(:millisecond))
end
defp wait_for_port(ip, port, timeout, start_time)
when is_binary(ip) and is_integer(port) and is_integer(timeout) and is_integer(start_time) do
if timeout + start_time < :os.system_time(:millisecond) do
{:error, :timeout}
else
if port_open?(ip, port) do
{:ok, :port_is_open}
else
# Sleep for 500 ms, then retry
:timer.sleep(500)
wait_for_port(ip, port, timeout, start_time)
end
end
end
defp port_open?(ip, port, timeout \\ 1000)
when is_binary(ip) and is_integer(port) and is_integer(timeout) do
case :gen_tcp.connect(~c"#{ip}", port, [:binary, active: false], timeout) do
{:ok, socket} ->
:gen_tcp.close(socket)
true
{:error, _reason} ->
false
end
end
end