Current section
Files
Jump to
Current section
Files
test/support/concord_test_helper.ex
defmodule Concord.TestHelper do
@moduledoc """
Helper module for running Concord tests against an isolated singleton VSR
cluster.
"""
alias Concord.Engine.VSR
@supervisor_key {__MODULE__, :supervisor}
def start_test_cluster do
stop_test_cluster()
{:ok, _started} = Application.ensure_all_started(:viewstamped_replication)
group_id = {:concord_test, System.unique_integer([:positive, :monotonic])}
opts = [
group_id: group_id,
replica_id: 1,
members: [%{id: 1, endpoint: {:local_test_endpoint, group_id}}],
transport: :local,
storage: :memory,
bootstrap: true,
retry_timeout: 10,
client_id: {:concord_test_client, group_id}
]
Application.put_env(:concord, :vsr, opts)
with {:ok, supervisor} <- VSR.Supervisor.start_link(opts) do
Process.unlink(supervisor)
:persistent_term.put(@supervisor_key, supervisor)
wait_for_cluster_ready()
end
end
def stop_test_cluster do
stored_supervisor = :persistent_term.get(@supervisor_key, nil)
stop_supervisor(stored_supervisor)
case Process.whereis(VSR.Supervisor) do
^stored_supervisor -> :ok
supervisor -> stop_supervisor(supervisor)
end
after
:persistent_term.erase(@supervisor_key)
clear_materialized_views()
end
def wait_for_cluster_ready(timeout \\ 10_000) do
until = System.monotonic_time(:millisecond) + timeout
case loop(until, fn ->
case VSR.status() do
{:ok, %{cluster: %{status: :normal}}} -> :ready
_result -> :not_ready
end
end) do
:ok -> :ok
:timeout -> {:error, :timeout}
end
end
defp loop(until, fun) do
case fun.() do
:ready ->
:ok
:not_ready ->
if System.monotonic_time(:millisecond) < until do
Process.sleep(10)
loop(until, fun)
else
:timeout
end
end
end
defp clear_materialized_views do
Enum.each(:ets.all(), fn table ->
if materialized_view?(table) do
try do
:ets.delete_all_objects(table)
rescue
ArgumentError -> :ok
end
end
end)
end
defp materialized_view?(table) when is_atom(table) do
name = Atom.to_string(table)
table in [:concord_store, :concord_current, :concord_history, :concord_leases] or
String.starts_with?(name, "concord_index_")
end
defp materialized_view?(_table), do: false
defp stop_supervisor(supervisor) when is_pid(supervisor) do
if Process.alive?(supervisor), do: Supervisor.stop(supervisor)
catch
:exit, _reason -> :ok
end
defp stop_supervisor(_supervisor), do: :ok
end