Packages
testcontainers
1.2.1
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/reaper.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.Reaper do
use GenServer
defstruct []
@moduledoc """
A GenServer that manages the lifecycle of the Ryuk container within the Testcontainers ecosystem.
The Reaper is responsible for ensuring that resources are cleaned up properly when test containers
are no longer needed. It communicates with the Ryuk container, a tool used by Testcontainers to
reap orphaned containers.
## Usage
The module is typically used in the context of integration tests where test containers are
employed. It starts the Ryuk container and registers filters for cleaning up resources.
It is meant to be started before interacting with any test containers and stopped after all tests
are concluded.
Note: This is an internal component and should not be used directly in tests. Instead, it's used
by the higher-level Testcontainers APIs.
"""
alias Testcontainers.ContainerBuilder
alias Testcontainers.Utils
alias Testcontainers.Container
@ryuk_filter_label "testcontainers-elixir-reap"
@doc """
Starts the Reaper process if it is not already running.
This function will start the Reaper unlinked (outside of the current supervision tree)
and wait until it's registered with the local process registry.
It's particularly useful for scenarios where the Reaper needs to be started without a
linked supervision strategy, often before the actual test scenarios are executed.
## Examples
iex> Testcontainers.Reaper.start_eager()
{:ok, pid}
## Options
The function accepts an optional list of options (`opts`) that are passed to the GenServer
initialization, though in the current implementation, these options are not used.
## Errors
If the Reaper fails to start, the function will return an error tuple.
## Note
This function is designed to be used in setup scenarios, possibly within test suite setup
callbacks.
"""
def start_eager(opts \\ []) do
case GenServer.whereis(__MODULE__) do
nil ->
start_unlinked(opts)
pid when is_pid(pid) ->
{:ok, pid}
end
end
def get_filter_label, do: @ryuk_filter_label
@impl true
def init(_) do
Process.flag(:trap_exit, true)
with {:ok, container} <- Container.run(%__MODULE__{}, on_exit: nil),
{:ok, socket} <- create_ryuk_socket(container) do
ryuk_container_id = container.container_id
Utils.log("Reaper initialized with containerId #{ryuk_container_id}")
# registers the label filter that ryuk uses to delete containers
send(self(), {:register, {"label", @ryuk_filter_label, "true"}})
{:ok, %{socket: socket, container: container, id: ryuk_container_id}}
end
end
@impl true
def handle_info({:register, {type, key, value}}, %{socket: socket} = state) do
:gen_tcp.send(
socket,
"#{:uri_string.quote(type)}=#{:uri_string.quote(key)}=#{:uri_string.quote(value)}" <> "\n"
)
case :gen_tcp.recv(socket, 0, 1_000) do
{:ok, "ACK\n"} ->
{:noreply, state}
{:error, reason} ->
{:error, reason}
end
end
defimpl ContainerBuilder, for: __MODULE__ do
def build(_, _) do
Container.new("testcontainers/ryuk:0.5.1")
|> Container.with_exposed_port(8080)
|> Container.with_environment("RYUK_PORT", "8080")
|> Container.with_bind_mount("/var/run/docker.sock", "/var/run/docker.sock", "rw")
end
end
defp create_ryuk_socket(%Container{} = container) do
host_port = Container.mapped_port(container, 8080)
:gen_tcp.connect(~c"localhost", host_port, [
:binary,
active: false,
packet: :line
])
end
defp start_unlinked(opts) do
spawn(fn -> GenServer.start(__MODULE__, opts, name: __MODULE__) end)
wait_for_start(__MODULE__, 10_000)
end
defp wait_for_start(name, timeout) do
wait_for_start(name, timeout, :timer.seconds(1))
end
defp wait_for_start(_name, 0, _interval), do: {:error, :timeout}
defp wait_for_start(name, remaining_time, interval) do
case GenServer.whereis(name) do
nil ->
Process.sleep(interval)
wait_for_start(name, remaining_time - interval, interval)
pid when is_pid(pid) ->
{:ok, pid}
end
end
end